Index: /tags/RHL_v1_1/psModules/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/.cvsignore	(revision 9533)
@@ -0,0 +1,27 @@
+autom4te.cache
+Makefile
+config.log
+config.status
+libtool
+Makefile.in
+aclocal.m4
+configure
+Doxyfile
+DoxygenLog
+docs
+man
+psmodules-config
+psmodules.pc
+bin
+include
+lib
+config.guess
+config.sub
+depcomp
+install-sh
+ltmain.sh
+missing
+psmodule.kdevelop.pcs
+psmodule-*.tar.gz
+psmodule-*.tar.bz2
+compile
Index: /tags/RHL_v1_1/psModules/AUTHORS
===================================================================
--- /tags/RHL_v1_1/psModules/AUTHORS	(revision 9533)
+++ /tags/RHL_v1_1/psModules/AUTHORS	(revision 9533)
@@ -0,0 +1,1 @@
+Maui High Performance Computing Center, University of Hawai'i
Index: /tags/RHL_v1_1/psModules/COPYING
===================================================================
--- /tags/RHL_v1_1/psModules/COPYING	(revision 9533)
+++ /tags/RHL_v1_1/psModules/COPYING	(revision 9533)
@@ -0,0 +1,6 @@
+(C) 2004 University of Hawai'i
+
+Permission to copy/distribute to be governed by:
+    Institute for Astronomy
+    2680 Woodlawn Drive
+    Honolulu, HI 96822-1897
Index: /tags/RHL_v1_1/psModules/ChangeLog
===================================================================
--- /tags/RHL_v1_1/psModules/ChangeLog	(revision 9533)
+++ /tags/RHL_v1_1/psModules/ChangeLog	(revision 9533)
@@ -0,0 +1,1 @@
+ 
Index: /tags/RHL_v1_1/psModules/Doxyfile.in
===================================================================
--- /tags/RHL_v1_1/psModules/Doxyfile.in	(revision 9533)
+++ /tags/RHL_v1_1/psModules/Doxyfile.in	(revision 9533)
@@ -0,0 +1,1079 @@
+# Doxyfile 1.3.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
+#---------------------------------------------------------------------------
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = "Pan-STARRS Module Library"
+
+# 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         = @VERSION@
+
+# 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       = @prefix@/docs/psmodules
+
+# 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:
+# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch,
+# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en
+# (Japanese with English messages), Korean, Norwegian, Polish, Portuguese,
+# Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# This tag can be used to specify the encoding used in the generated output.
+# The encoding is not always determined by the language that is chosen,
+# but also whether or not the output is meant for Windows or non-Windows users.
+# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES
+# forces the Windows encoding (this is the default for the Windows binary),
+# whereas setting the tag to NO uses a Unix-style encoding (the default for
+# all platforms other than Windows).
+
+USE_WINDOWS_ENCODING   = NO
+
+# 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
+
+# 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    = YES
+
+# 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. It is allowed to use relative paths in the argument list.
+
+STRIP_FROM_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 the Qt-style comments (thus requiring an
+# explict @brief command for a brief description.
+
+JAVADOC_AUTOBRIEF      = YES
+
+# 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         = YES
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented
+# member inherits the documentation from any documented member that it
+# reimplements.
+
+INHERIT_DOCS           = YES
+
+# 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
+
+# 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  = YES
+
+# 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
+
+# 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            = 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            = YES
+
+# 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  = 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 
+# 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
+
+# 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
+
+#---------------------------------------------------------------------------
+# 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
+
+# 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.
+
+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           = DoxygenLog
+
+#---------------------------------------------------------------------------
+# 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
+
+# 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
+
+FILE_PATTERNS          = *.h *.dox
+
+# 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.
+
+EXCLUDE_PATTERNS       = *_wrap.c
+
+# 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.
+
+INPUT_FILTER           = 
+
+# 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.
+
+SOURCE_BROWSER         = YES
+
+# 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    = NO
+
+# 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 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     = YES
+
+# 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
+
+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 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 dir.
+
+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             = YES
+
+# 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   = 10
+
+# 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          = YES
+
+# 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             = letter
+
+# 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         = times
+
+# 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         = YES
+
+# 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           = YES
+
+# 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 optimised 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 assigments. 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              = YES
+
+#---------------------------------------------------------------------------
+# 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. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+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                = 
+
+#---------------------------------------------------------------------------
+# 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_PREDEFINED 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.
+
+PREDEFINED             = DOXYGEN
+
+# 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::addtions 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              = @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 superceded 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
+
+# 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               = YES
+
+# 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 UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similiar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = YES
+
+# If set to YES, the inheritance and collaboration graphs will show the
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = YES
+
+# 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 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 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
+
+# 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 on 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_WIDTH tag can be used to set the maximum allowed width 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_WIDTH    = 1024
+
+# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height 
+# (in pixels) of the graphs generated by dot. If a graph becomes larger than 
+# this value, doxygen will try to truncate the graph, so that it fits within 
+# the specified constraint. Beware that most browsers cannot cope with very 
+# large images.
+
+MAX_DOT_GRAPH_HEIGHT   = 1024
+
+# 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 a graph may be further truncated if the graph's image dimensions are 
+# not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT). 
+# If 0 is used for the depth value (the default), the graph is not depth-constrained.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# 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::addtions 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: /tags/RHL_v1_1/psModules/INSTALL
===================================================================
--- /tags/RHL_v1_1/psModules/INSTALL	(revision 9533)
+++ /tags/RHL_v1_1/psModules/INSTALL	(revision 9533)
@@ -0,0 +1,167 @@
+Basic Installation
+==================
+
+   These are generic installation instructions.
+
+   The `configure' shell script attempts to guess correct values for
+various system-dependent variables used during compilation.  It uses
+those values to create a `Makefile' in each directory of the package.
+It may also create one or more `.h' files containing system-dependent
+definitions.  Finally, it creates a shell script `config.status' that
+you can run in the future to recreate the current configuration, a file
+`config.cache' that saves the results of its tests to speed up
+reconfiguring, and a file `config.log' containing compiler output
+(useful mainly for debugging `configure').
+
+   If you need to do unusual things to compile the package, please try
+to figure out how `configure' could check whether to do them, and mail
+diffs or instructions to the address given in the `README' so they can
+be considered for the next release.  If at some point `config.cache'
+contains results you don't want to keep, you may remove or edit it.
+
+   The file `configure.in' is used to create `configure' by a program
+called `autoconf'.  You only need `configure.in' if you want to change
+it or regenerate `configure' using a newer version of `autoconf'.
+
+The simplest way to compile this package is:
+
+  1. `cd' to the directory containing the package's source code and type
+     `./configure' to configure the package for your system.  If you're
+     using `csh' on an old version of System V, you might need to type
+     `sh ./configure' instead to prevent `csh' from trying to execute
+     `configure' itself.
+
+     Running `configure' takes a while.  While running, it prints some
+     messages telling which features it is checking for.
+
+  2. Type `make' to compile the package.
+
+  3. Type `make install' to install the programs and any data files and
+     documentation.
+
+  4. You can remove the program binaries and object files from the
+     source code directory by typing `make clean'.  
+
+Compilers and Options
+=====================
+
+   Some systems require unusual options for compilation or linking that
+the `configure' script does not know about.  You can give `configure'
+initial values for variables by setting them in the environment.  Using
+a Bourne-compatible shell, you can do that on the command line like
+this:
+     CC=c89 CFLAGS=-O2 LIBS=-lposix ./configure
+
+Or on systems that have the `env' program, you can do it like this:
+     env CPPFLAGS=-I/usr/local/include LDFLAGS=-s ./configure
+
+Compiling For Multiple Architectures
+====================================
+
+   You can compile the package for more than one kind of computer at the
+same time, by placing the object files for each architecture in their
+own directory.  To do this, you must use a version of `make' that
+supports the `VPATH' variable, such as GNU `make'.  `cd' to the
+directory where you want the object files and executables to go and run
+the `configure' script.  `configure' automatically checks for the
+source code in the directory that `configure' is in and in `..'.
+
+   If you have to use a `make' that does not supports the `VPATH'
+variable, you have to compile the package for one architecture at a time
+in the source code directory.  After you have installed the package for
+one architecture, use `make distclean' before reconfiguring for another
+architecture.
+
+Installation Names
+==================
+
+   By default, `make install' will install the package's files in
+`/usr/local/bin', `/usr/local/man', etc.  You can specify an
+installation prefix other than `/usr/local' by giving `configure' the
+option `--prefix=PATH'.
+
+   You can specify separate installation prefixes for
+architecture-specific files and architecture-independent files.  If you
+give `configure' the option `--exec-prefix=PATH', the package will use
+PATH as the prefix for installing programs and libraries.
+Documentation and other data files will still use the regular prefix.
+
+   If the package supports it, you can cause programs to be installed
+with an extra prefix or suffix on their names by giving `configure' the
+option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'.
+
+Optional Features
+=================
+
+   Some packages pay attention to `--enable-FEATURE' options to
+`configure', where FEATURE indicates an optional part of the package.
+They may also pay attention to `--with-PACKAGE' options, where PACKAGE
+is something like `gnu-as' or `x' (for the X Window System).  The
+`README' should mention any `--enable-' and `--with-' options that the
+package recognizes.
+
+   For packages that use the X Window System, `configure' can usually
+find the X include and library files automatically, but if it doesn't,
+you can use the `configure' options `--x-includes=DIR' and
+`--x-libraries=DIR' to specify their locations.
+
+Specifying the System Type
+==========================
+
+   There may be some features `configure' can not figure out
+automatically, but needs to determine by the type of host the package
+will run on.  Usually `configure' can figure that out, but if it prints
+a message saying it can not guess the host type, give it the
+`--host=TYPE' option.  TYPE can either be a short name for the system
+type, such as `sun4', or a canonical name with three fields:
+     CPU-COMPANY-SYSTEM
+
+See the file `config.sub' for the possible values of each field.  If
+`config.sub' isn't included in this package, then this package doesn't
+need to know the host type.
+
+   If you are building compiler tools for cross-compiling, you can also
+use the `--target=TYPE' option to select the type of system they will
+produce code for and the `--build=TYPE' option to select the type of
+system on which you are compiling the package.
+
+Sharing Defaults
+================
+
+   If you want to set default values for `configure' scripts to share,
+you can create a site shell script called `config.site' that gives
+default values for variables like `CC', `cache_file', and `prefix'.
+`configure' looks for `PREFIX/share/config.site' if it exists, then
+`PREFIX/etc/config.site' if it exists.  Or, you can set the
+`CONFIG_SITE' environment variable to the location of the site script.
+A warning: not all `configure' scripts look for a site script.
+
+Operation Controls
+==================
+
+   `configure' recognizes the following options to control how it
+operates.
+
+`--cache-file=FILE'
+     Use and save the results of the tests in FILE instead of
+     `./config.cache'.  Set FILE to `/dev/null' to disable caching, for
+     debugging `configure'.
+
+`--help'
+     Print a summary of the options to `configure', and exit.
+
+`--quiet'
+`--silent'
+`-q'
+     Do not print messages saying which checks are being made.
+
+`--srcdir=DIR'
+     Look for the package's source code in directory DIR.  Usually
+     `configure' can determine that directory automatically.
+
+`--version'
+     Print the version of Autoconf used to generate the `configure'
+     script, and exit.
+
+`configure' also accepts some other, not widely useful, options.
+
Index: /tags/RHL_v1_1/psModules/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/Makefile.am	(revision 9533)
@@ -0,0 +1,34 @@
+SUBDIRS = src test
+
+bin_SCRIPTS = psmodules-config
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= psmodules.pc
+
+EXTRA_DIST = \
+	Doxyfile.in \
+	psmodules-config.in \
+	psmodules.pc.in \
+	autogen.sh
+
+if DOXYGEN
+
+docs: Doxyfile $(prefix)/docs/psmodules $(mandir)/man3
+	doxygen Doxyfile
+	mv -f $(prefix)/docs/psmodules/man/man3/* $(mandir)/man3
+	rm -rf $(prefix)/docs/psmodules/man
+
+$(prefix)/docs/psmodules:
+	mkdir -p $(prefix)/docs/psmodules
+
+$(mandir)/man3:
+	mkdir -p $(mandir)/man3
+
+uninstall-hook:
+	rm -rf $(prefix)/docs/psmodules/*
+
+CLEANFILES = $(prefix)/docs/psmodules/* *~
+
+endif
+
+test: check
Index: /tags/RHL_v1_1/psModules/NEWS
===================================================================
--- /tags/RHL_v1_1/psModules/NEWS	(revision 9533)
+++ /tags/RHL_v1_1/psModules/NEWS	(revision 9533)
@@ -0,0 +1,1 @@
+ 
Index: /tags/RHL_v1_1/psModules/README
===================================================================
--- /tags/RHL_v1_1/psModules/README	(revision 9533)
+++ /tags/RHL_v1_1/psModules/README	(revision 9533)
@@ -0,0 +1,1 @@
+
Index: /tags/RHL_v1_1/psModules/TODO
===================================================================
--- /tags/RHL_v1_1/psModules/TODO	(revision 9533)
+++ /tags/RHL_v1_1/psModules/TODO	(revision 9533)
@@ -0,0 +1,1 @@
+ 
Index: /tags/RHL_v1_1/psModules/autogen.sh
===================================================================
--- /tags/RHL_v1_1/psModules/autogen.sh	(revision 9533)
+++ /tags/RHL_v1_1/psModules/autogen.sh	(revision 9533)
@@ -0,0 +1,114 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=psmodules
+TEST_TYPE=-f
+FILE=psmodules.pc.in
+
+DIE=0
+
+if [ "`which glibtoolize 2> /dev/null`" != "" ]
+ then LIBTOOLIZE=glibtoolize
+ else LIBTOOLIZE=libtoolize
+fi
+
+ACLOCAL=aclocal
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+# bypass taps bootstrap.sh
+cd ./test/tap
+$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /tags/RHL_v1_1/psModules/configure.ac
===================================================================
--- /tags/RHL_v1_1/psModules/configure.ac	(revision 9533)
+++ /tags/RHL_v1_1/psModules/configure.ac	(revision 9533)
@@ -0,0 +1,150 @@
+AC_PREREQ(2.59)
+
+AC_INIT([psmodules],[0.12.99],[http://pan-starrs.ifa.hawaii.edu/bugzilla])
+AC_CONFIG_SRCDIR([psmodules.pc.in])
+
+dnl this enables the building of libtap
+AC_CONFIG_SUBDIRS([test/tap])
+
+AM_INIT_AUTOMAKE([1.7 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+dnl otherise AC_PROG_CC will default CFLAGS to "-g -02"
+if test -z ${CFLAGS} ; then
+  CFLAGS=""
+fi
+AC_SUBST([CFLAGS])
+
+AC_LANG(C)
+AC_PROG_CC
+dnl FIXME document why GNU_SOURCE is being set
+AC_GNU_SOURCE
+AC_PROG_INSTALL
+AM_PROG_LIBTOOL
+
+dnl enable largefile supports
+AC_SYS_LARGEFILE
+AC_FUNC_FSEEKO
+
+AC_PREFIX_DEFAULT([`pwd`])
+
+dnl cflags --------------------------------------------------------------------
+dnl FIXME document why -std=gnu99 is being set
+
+AC_MSG_CHECKING([C99 flag])
+if test "$GCC" = "yes" ; then
+  AM_CFLAGS="-std=gnu99"
+  AC_MSG_RESULT([-std=gnu99])
+else
+  AM_CFLAGS="-std=c99"
+  AC_MSG_RESULT([-std=c99])
+fi
+AM_CFLAGS="${AM_CFLAGS=} -Wall -Werror"
+AC_SUBST([AM_CFLAGS])
+
+dnl ------------------- PERL options ---------------------
+  AC_ARG_WITH(perl,
+    [AS_HELP_STRING(--with-perl=FILE,Specify location of PERL executable.)],
+    [AC_CHECK_PROG(PERL, $withval, $withval)],
+    [AC_CHECK_PROG(PERL, perl, `which perl`)])
+    if test "$PERL" == ""
+    then
+      AC_MSG_ERROR([PERL is required.  Use --with-perl to specify its install location.])
+    fi
+    AC_SUBST(PERL,$PERL)
+
+SRCPATH='${top_srcdir}/src'
+SRCDIRS="config concepts camera astrom detrend imcombine objects"
+# escape two escapes at this level so \\ gets passed to the shell and \ to perl
+SRCINC=`echo "${SRCDIRS=}" | ${PERL} -pe "s|(\w+)|-I\\\\${SRCPATH=}/\1|g"`
+SRCINC="-I${SRCPATH=} ${SRCINC=}"
+SRCSUBLIBS=`echo "${SRCDIRS=}" | ${PERL} -pe "s|(\w+)|\1/libpsmodules\1.la|g"`
+AC_SUBST(SRCSUBLIBS,${SRCSUBLIBS=})
+AC_SUBST(SRCINC,${SRCINC=})
+AC_SUBST([SRCDIRS],${SRCDIRS=})
+
+dnl handle optimized building
+AC_ARG_ENABLE(optimize,
+  [AS_HELP_STRING(--enable-optimize,enable compiler optimization)],
+  [AC_MSG_RESULT(compile optimization enabled)
+   CFLAGS="${CFLAGS=} -pipe -O2 -g"
+   PSLIB_CFLAGS="${PSLIB_CFLAGS=} -DPS_NO_TRACE"],
+  [AC_MSG_RESULT([compile optimization disabled])
+    if test x"${CFLAGS}" == x; then
+      CFLAGS="-pipe -O0 -g"
+    fi
+  ]
+)
+
+dnl handle profiler building
+AC_ARG_ENABLE(profile,
+  [AS_HELP_STRING(--enable-profile,enable compiler profiler information inclusion)],
+  [AC_MSG_RESULT(compile optimization enabled)
+   CFLAGS="${CFLAGS=} -pg -g"
+   LDFLAGS="${LDFLAGS=} -pg"]
+)
+
+dnl handle path coverage checking
+AC_ARG_ENABLE(coverage,
+  [AS_HELP_STRING(--enable-coverage,enable path coverage checking)],
+  [AC_MSG_RESULT(path coverage enabled)
+   CFLAGS="${CFLAGS=} -fprofile-arcs -ftest-coverage -pg"]
+)
+
+dnl CFLAGS="${CFLAGS=} -fprofile-arcs -ftest-coverage -pg"]
+
+dnl doxygen -------------------------------------------------------------------
+
+AC_CHECK_PROG([doxygen], [doxygen], [true], [false])
+AM_CONDITIONAL(DOXYGEN, test x$doxygen = xtrue)
+
+dnl pslib ---------------------------------------------------------------------
+AC_ARG_WITH(pslib-config,
+[  --with-pslib-config=FILE  Specify location of psLib-config script],
+[PSLIB_CONFIG=$withval])
+
+if test -z ${PSLIB_CONFIG} ; then
+  PKG_CHECK_MODULES([PSLIB], [pslib >= 0.12.0])
+else
+  AC_CHECK_FILE($PSLIB_CONFIG,[],
+    [AC_MSG_ERROR([psLib is required.  If not in path, use --with-pslib-config to specify pslib-config script location.])])
+  AC_MSG_CHECKING([PSLIB_CFLAGS])
+  PSLIB_CFLAGS="`${PSLIB_CONFIG} --cflags`"
+  AC_MSG_RESULT([${PSLIB_CFLAGS}])
+  AC_MSG_CHECKING([PSLIB_LIBS])
+  PSLIB_LIBS="`${PSLIB_CONFIG} --libs`"
+  AC_MSG_RESULT([${PSLIB_LIBS}])
+fi
+
+PSMODULES_CFLAGS="${PSMODULES_CFLAGS=} ${PSLIB_CFLAGS}"
+PSMODULES_LIBS="${PSMODULES_LIBS=} ${PSLIB_LIBS}"
+dnl ---------------------------------------------------------------------------
+
+AC_SUBST([PSMODULES_CFLAGS])
+AC_SUBST([PSMODULES_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  src/astrom/Makefile
+  src/camera/Makefile
+  src/config/Makefile
+  src/concepts/Makefile
+  src/detrend/Makefile
+  src/imcombine/Makefile
+  src/objects/Makefile
+  test/Makefile
+  test/astrom/Makefile
+  test/config/Makefile
+  test/camera/Makefile
+  test/concepts/Makefile
+  test/detrend/Makefile
+  test/imcombine/Makefile
+  test/objects/Makefile
+  Doxyfile
+  psmodules-config
+  psmodules.pc
+])
+
+AC_OUTPUT
Index: /tags/RHL_v1_1/psModules/psmodule.kdevelop
===================================================================
--- /tags/RHL_v1_1/psModules/psmodule.kdevelop	(revision 9533)
+++ /tags/RHL_v1_1/psModules/psmodule.kdevelop	(revision 9533)
@@ -0,0 +1,180 @@
+<?xml version = '1.0'?>
+<kdevelop>
+  <general>
+    <author>Robert Daniel DeSonia</author>
+    <email>desonia@mhpcc.hawaii.edu</email>
+    <version>0.1</version>
+    <projectmanagement>KDevAutoProject</projectmanagement>
+    <primarylanguage>C</primarylanguage>
+    <keywords>
+      <keyword>C</keyword>
+      <keyword>Code</keyword>
+    </keywords>
+    <projectdirectory>.</projectdirectory>
+    <absoluteprojectpath>false</absoluteprojectpath>
+    <description/>
+    <ignoreparts/>
+    <secondaryLanguages/>
+  </general>
+  <kdevautoproject>
+    <general>
+      <activetarget>src/psmodule</activetarget>
+      <useconfiguration>debug</useconfiguration>
+    </general>
+    <run>
+      <mainprogram>src/tst_pmFlatField</mainprogram>
+      <terminal>true</terminal>
+      <directoryradio>executable</directoryradio>
+      <customdirectory>/</customdirectory>
+      <programargs/>
+      <autocompile>true</autocompile>
+      <envvars/>
+    </run>
+    <configurations>
+      <optimized>
+        <builddir>optimized</builddir>
+        <ccompiler>kdevgccoptions</ccompiler>
+        <cxxcompiler>kdevgppoptions</cxxcompiler>
+        <f77compiler>kdevg77options</f77compiler>
+        <cflags>-O2 -g0</cflags>
+      </optimized>
+      <debug>
+        <configargs>--enable-debug=full</configargs>
+        <builddir>debug</builddir>
+        <ccompiler>kdevgccoptions</ccompiler>
+        <cxxcompiler>kdevgppoptions</cxxcompiler>
+        <f77compiler>kdevg77options</f77compiler>
+        <cflags>-O0 -g3</cflags>
+      </debug>
+    </configurations>
+    <make>
+      <envvars>
+        <envvar value="1" name="WANT_AUTOCONF_2_5" />
+        <envvar value="1" name="WANT_AUTOMAKE_1_6" />
+      </envvars>
+      <abortonerror>false</abortonerror>
+      <numberofjobs>1</numberofjobs>
+      <dontact>false</dontact>
+      <makebin/>
+    </make>
+  </kdevautoproject>
+  <kdevdoctreeview>
+    <ignoretocs>
+      <toc>ada</toc>
+      <toc>ada_bugs_gcc</toc>
+      <toc>bash</toc>
+      <toc>bash_bugs</toc>
+      <toc>clanlib</toc>
+      <toc>w3c-dom-level2-html</toc>
+      <toc>fortran_bugs_gcc</toc>
+      <toc>gnome1</toc>
+      <toc>gnustep</toc>
+      <toc>gtk</toc>
+      <toc>gtk_bugs</toc>
+      <toc>haskell</toc>
+      <toc>haskell_bugs_ghc</toc>
+      <toc>java_bugs_gcc</toc>
+      <toc>java_bugs_sun</toc>
+      <toc>kde2book</toc>
+      <toc>libstdc++</toc>
+      <toc>opengl</toc>
+      <toc>pascal_bugs_fp</toc>
+      <toc>php</toc>
+      <toc>php_bugs</toc>
+      <toc>perl</toc>
+      <toc>perl_bugs</toc>
+      <toc>python</toc>
+      <toc>python_bugs</toc>
+      <toc>qt-kdev3</toc>
+      <toc>ruby</toc>
+      <toc>ruby_bugs</toc>
+      <toc>sdl</toc>
+      <toc>stl</toc>
+      <toc>w3c-svg</toc>
+      <toc>sw</toc>
+      <toc>w3c-uaag10</toc>
+      <toc>wxwidgets_bugs</toc>
+    </ignoretocs>
+    <ignoreqt_xml>
+      <toc>Guide to the Qt Translation Tools</toc>
+      <toc>Qt Assistant Manual</toc>
+      <toc>Qt Designer Manual</toc>
+      <toc>Qt Reference Documentation</toc>
+      <toc>qmake User Guide</toc>
+    </ignoreqt_xml>
+    <ignoredoxygen>
+      <toc>KDE Libraries (Doxygen)</toc>
+    </ignoredoxygen>
+    <projectdoc>
+      <userdocDir>html/</userdocDir>
+      <apidocDir>html/</apidocDir>
+    </projectdoc>
+    <ignorekdocs/>
+    <ignoredevhelp/>
+  </kdevdoctreeview>
+  <kdevfilecreate>
+    <filetypes/>
+    <useglobaltypes>
+      <type ext="c" />
+      <type ext="h" />
+    </useglobaltypes>
+  </kdevfilecreate>
+  <kdevfileview>
+    <groups>
+      <group pattern="*.h" name="Header files" />
+      <group pattern="*.c" name="Source files" />
+      <hidenonprojectfiles>false</hidenonprojectfiles>
+      <hidenonlocation>false</hidenonlocation>
+    </groups>
+    <tree>
+      <hidepatterns>*.o,*.lo,CVS</hidepatterns>
+      <hidenonprojectfiles>false</hidenonprojectfiles>
+      <showvcsfields>false</showvcsfields>
+    </tree>
+  </kdevfileview>
+  <kdevcppsupport>
+    <references/>
+    <codecompletion>
+      <includeGlobalFunctions>true</includeGlobalFunctions>
+      <includeTypes>true</includeTypes>
+      <includeEnums>true</includeEnums>
+      <includeTypedefs>false</includeTypedefs>
+      <automaticCodeCompletion>true</automaticCodeCompletion>
+      <automaticArgumentsHint>true</automaticArgumentsHint>
+      <automaticHeaderCompletion>true</automaticHeaderCompletion>
+      <codeCompletionDelay>250</codeCompletionDelay>
+      <argumentsHintDelay>400</argumentsHintDelay>
+      <headerCompletionDelay>250</headerCompletionDelay>
+    </codecompletion>
+  </kdevcppsupport>
+  <cppsupportpart>
+    <filetemplates>
+      <interfacesuffix>.h</interfacesuffix>
+      <implementationsuffix>.cpp</implementationsuffix>
+    </filetemplates>
+  </cppsupportpart>
+  <kdevdebugger>
+    <general>
+      <programargs/>
+      <gdbpath/>
+      <dbgshell/>
+      <configGdbScript/>
+      <runShellScript/>
+      <runGdbScript/>
+      <breakonloadinglibs>true</breakonloadinglibs>
+      <separatetty>false</separatetty>
+      <floatingtoolbar>false</floatingtoolbar>
+    </general>
+    <display>
+      <staticmembers>false</staticmembers>
+      <demanglenames>true</demanglenames>
+    </display>
+  </kdevdebugger>
+  <kdevcvsservice>
+    <recursivewhenupdate>true</recursivewhenupdate>
+    <prunedirswhenupdate>true</prunedirswhenupdate>
+    <createdirswhenupdate>true</createdirswhenupdate>
+    <recursivewhencommitremove>true</recursivewhencommitremove>
+    <revertoptions>-C</revertoptions>
+  </kdevcvsservice>
+</kdevelop>
Index: /tags/RHL_v1_1/psModules/psmodules-config.in
===================================================================
--- /tags/RHL_v1_1/psModules/psmodules-config.in	(revision 9533)
+++ /tags/RHL_v1_1/psModules/psmodules-config.in	(revision 9533)
@@ -0,0 +1,76 @@
+#! /bin/sh
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@/@PACKAGE_NAME@
+
+usage()
+{
+    cat <<EOF
+Usage: pslib-config [OPTION]
+
+Known values for OPTION are:
+
+  --prefix		print psLib installation prefix
+  --libs		print library linking information
+  --cflags		print pre-processor and compiler flags
+  --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} @PSMODULES_CFLAGS@
+       	;;
+
+    --libs)
+       	echo -L${libdir} -lpsmodules @PSMODULES_LIBS@
+       	;;
+
+    --deps)
+       	echo @LDFLAGS@
+       	;;
+    *)
+	usage
+	exit 1
+	;;
+    esac
+    shift
+done
+
+exit 0
Index: /tags/RHL_v1_1/psModules/psmodules.pc.in
===================================================================
--- /tags/RHL_v1_1/psModules/psmodules.pc.in	(revision 9533)
+++ /tags/RHL_v1_1/psModules/psmodules.pc.in	(revision 9533)
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@/@PACKAGE_NAME@
+
+Name: @PACKAGE_NAME@
+Description: Pan-STARRS Module Library
+Version: @VERSION@
+Libs: -L${libdir} -lpsmodules @PSMODULES_LIBS@
+Cflags: -I${includedir} @PSMODULES_CFLAGS@
Index: /tags/RHL_v1_1/psModules/src/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/src/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/.cvsignore	(revision 9533)
@@ -0,0 +1,9 @@
+.deps
+.libs
+Makefile
+config.h
+stamp-h1
+Makefile.in
+config.h.in
+*.la
+*.lo
Index: /tags/RHL_v1_1/psModules/src/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/src/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/Makefile.am	(revision 9533)
@@ -0,0 +1,14 @@
+SUBDIRS = $(SRCDIRS)
+lib_LTLIBRARIES = libpsmodules.la
+
+libpsmodules_la_SOURCES = dummy.c
+libpsmodules_la_CPPFLAGS = $(SRCINC)
+libpsmodules_la_LIBADD = $(SRCSUBLIBS) $(PSMODULES_LIBS)
+libpsmodules_la_DEPENDENCIES = $(SRCSUBLIBS)
+
+pkginclude_HEADERS = \
+	psmodules.h
+
+EXTRA_DIST = psErrorCodes.dat
+
+CLEANFILES = *~
Index: /tags/RHL_v1_1/psModules/src/astrom/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/src/astrom/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/astrom/.cvsignore	(revision 9533)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/RHL_v1_1/psModules/src/astrom/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/src/astrom/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/astrom/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+noinst_LTLIBRARIES = libpsmodulesastrom.la
+
+libpsmodulesastrom_la_CPPFLAGS = $(SRCINC) $(PSMODULES_CFLAGS) -I../pslib/
+libpsmodulesastrom_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulesastrom_la_SOURCES  = \
+	pmAstrometryObjects.c \
+	pmAstrometryDistortion.c
+
+pkginclude_HEADERS = \
+	pmAstrometryObjects.h \
+	pmAstrometryDistortion.h
+
+CLEANFILES = *~
Index: /tags/RHL_v1_1/psModules/src/astrom/pmAstrometryDistortion.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/astrom/pmAstrometryDistortion.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/astrom/pmAstrometryDistortion.c	(revision 9533)
@@ -0,0 +1,297 @@
+/** @file  pmAstrometryDistortion.c
+*
+*  @brief This file defines the basic types for measuring the focal-plane distortion.
+*
+*  @ingroup AstroImage
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-09-15 09:49:01 $
+*
+*  Copyright 2006 Institute for Astronomy, University of Hawaii
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/******************************************************************************/
+/*  INCLUDE FILES                                                             */
+/******************************************************************************/
+
+//#include <stdio.h>
+//#include <string.h>
+//#include <math.h>
+//#include <unistd.h>   // for unlink
+#include <pslib.h>
+#include "pmFPA.h"
+#include "pmAstrometryObjects.h"
+#include "pmAstrometryDistortion.h"
+
+static void pmAstromGradientFree (pmAstromGradient *grad)
+{
+
+    if (grad == NULL)
+        return;
+
+    return;
+}
+
+pmAstromGradient *pmAstromGradientAlloc ()
+{
+
+    pmAstromGradient *gradient = psAlloc (sizeof(pmAstromGradient));
+    psMemSetDeallocator(gradient, (psFreeFunc) pmAstromGradientFree);
+
+    // XXX init any of the data values?
+
+    return (gradient);
+}
+
+psArray *pmAstromMeasureGradients(psArray *grads, psArray *rawstars, psArray *refstars, psArray *matches, psMetadata *config)
+{
+
+    if (grads == NULL) {
+        grads = psArrayAlloc (100);
+    }
+
+    // determine range
+    // XXX for the moment, I'm hard-wiring this for megacam
+    // XXX we need to get the chip dimensions from the metadata somewhere
+    int Nx = 2;
+    int Ny = 2;
+    int DX = 2080 / Nx;
+    int DY = 4600 / Ny;
+
+    psPolynomial2D *local = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, 1, 1);
+    local->mask[1][1] = 1;
+
+    // measure gradient for fractional chip regions
+    for (int nx = 0; nx < Nx; nx++) {
+        for (int ny = 0; ny < Ny; ny++) {
+            int Xmin = nx*DX;
+            int Xmax = Xmin + DX;
+            int Ymin = ny*DY;
+            int Ymax = Ymin + DY;
+
+            psVector *L  = psVectorAlloc (100, PS_TYPE_F32);
+            psVector *M  = psVectorAlloc (100, PS_TYPE_F32);
+            psVector *dP = psVectorAlloc (100, PS_TYPE_F32);
+            psVector *dQ = psVectorAlloc (100, PS_TYPE_F32);
+            int Npts = 0;
+
+            // XXX this is a bit inefficient: first sorting by X or Y could speed this up.
+            // XXX or assigning to a segment in a single pass first
+            // select the stars within this chip region
+            for (int i = 0; i < matches->n; i++) {
+
+                pmAstromMatch *match = matches->data[i];
+
+                pmAstromObj *raw = rawstars->data[match->raw];
+
+                if (raw->chip->x < Xmin)
+                    continue;
+                if (raw->chip->x > Xmax)
+                    continue;
+                if (raw->chip->y < Ymin)
+                    continue;
+                if (raw->chip->y > Ymax)
+                    continue;
+
+                pmAstromObj *ref = refstars->data[match->ref];
+
+                L->data.F32[Npts] = ref->FP->x;
+                M->data.F32[Npts] = ref->FP->y;
+
+                dP->data.F32[Npts] = ref->TP->x - raw->TP->x;
+                dQ->data.F32[Npts] = ref->TP->y - raw->TP->y;
+
+                psVectorExtend (L, 100, 1);
+                psVectorExtend (M, 100, 1);
+                psVectorExtend (dP, 100, 1);
+                psVectorExtend (dQ, 100, 1);
+                Npts++;
+            }
+            if (Npts < 5)
+                continue;
+
+            // fit the collection of positions and offsets with a local 1st order gradient
+            // 3hi/3lo sigma clipping on the rflux vs metric fit
+            psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+            psVector *mask = psVectorAlloc (Npts, PS_TYPE_MASK);
+            mask->n = Npts;
+            psVectorInit (mask, 0);
+
+            pmAstromGradient *grad = pmAstromGradientAlloc ();
+
+            psVectorStats (stats, L, NULL, NULL, 0);
+            grad->FP.x = stats->sampleMedian;
+
+            psVectorStats (stats, M, NULL, NULL, 0);
+            grad->FP.y = stats->sampleMedian;
+
+            psVectorClipFitPolynomial2D (local, stats, mask, 0xff, dP, NULL, L, M);
+
+            grad->dTPdL.x = local->coeff[1][0];
+            grad->dTPdM.x = local->coeff[0][1];
+
+            psVectorClipFitPolynomial2D (local, stats, mask, 0xff, dQ, NULL, L, M);
+
+            grad->dTPdL.y = local->coeff[1][0];
+            grad->dTPdM.y = local->coeff[0][1];
+
+            psArrayAdd (grads, 100, grad);
+        }
+    }
+    return grads;
+}
+
+bool pmAstromFitDistortion(pmFPA *fpa, psArray *grads, psMetadata *config)
+{
+
+    psPolynomial2D *localX = NULL;
+    psPolynomial2D *localY = NULL;
+
+    // assign the gradient elements to psVectors for fitting
+    psVector *dPdL = psVectorAlloc (grads->n, PS_TYPE_F32);
+    psVector *dQdL = psVectorAlloc (grads->n, PS_TYPE_F32);
+    psVector *dPdM = psVectorAlloc (grads->n, PS_TYPE_F32);
+    psVector *dQdM = psVectorAlloc (grads->n, PS_TYPE_F32);
+    psVector *L = psVectorAlloc (grads->n, PS_TYPE_F32);
+    psVector *M = psVectorAlloc (grads->n, PS_TYPE_F32);
+
+    for (int i = 0; i < grads->n; i++) {
+
+        pmAstromGradient *grad = grads->data[i];
+
+        dPdL->data.F32[i] = grad->dTPdL.x;
+        dQdL->data.F32[i] = grad->dTPdL.y;
+
+        dPdM->data.F32[i] = grad->dTPdM.x;
+        dQdM->data.F32[i] = grad->dTPdM.y;
+
+        L->data.F32[i] = grad->FP.x;
+        M->data.F32[i] = grad->FP.y;
+    }
+    dPdL->n = dQdL->n = dPdM->n = dQdM->n = grads->n;
+    L->n = M->n = grads->n;
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    psVector *mask = psVectorAlloc (grads->n, PS_TYPE_MASK);
+    mask->n = grads->n;
+    psVectorInit (mask, 0);
+
+    // the order of the gradient fits need to be 1 less than the distortion term
+    // determine the gradient order(s) from the fpa->toTP structure
+    localX = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, fpa->toTangentPlane->x->nX-1, fpa->toTangentPlane->x->nY);
+    localY = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, fpa->toTangentPlane->x->nX,   fpa->toTangentPlane->x->nY-1);
+
+    // set masks based on fpa->toTP
+    for (int i = 0; i < fpa->toTangentPlane->x->nX; i++) {
+        for (int j = 0; j < fpa->toTangentPlane->x->nY; j++) {
+            if (fpa->toTangentPlane->x->mask[i][j][0][0]) {
+                localX->mask[i-1][j] = 1;
+                localY->mask[i][j-1] = 1;
+            }
+        }
+    }
+
+    // fit the local gradients in each direction
+    psVectorClipFitPolynomial2D (localX, stats, mask, 0xff, dPdL, NULL, L, M);
+    psVectorClipFitPolynomial2D (localY, stats, mask, 0xff, dPdM, NULL, L, M);
+
+    // XXX test: generate fit
+    psVector *dPdLf = psPolynomial2DEvalVector (localX, L, M);
+    psVector *dPdMf = psPolynomial2DEvalVector (localY, L, M);
+
+    // update fpa->toTP distortion terms
+    fpa->toTangentPlane->x->coeff[0][0][0][0] = 0;
+    for (int i = 1; i < fpa->toTangentPlane->x->nX; i++) {
+        if (!fpa->toTangentPlane->x->mask[i][0][0][0]) {
+            fpa->toTangentPlane->x->coeff[i][0][0][0] = localX->coeff[i-1][0] / i;
+        }
+    }
+    for (int j = 1; j < fpa->toTangentPlane->x->nY; j++) {
+        if (!fpa->toTangentPlane->x->mask[0][j][0][0]) {
+            fpa->toTangentPlane->x->coeff[0][j][0][0] = localY->coeff[0][j-1] / j;
+        }
+    }
+    for (int i = 1; i < fpa->toTangentPlane->x->nX; i++) {
+        for (int j = 1; j < fpa->toTangentPlane->x->nY; j++) {
+            if (!fpa->toTangentPlane->x->mask[i][j][0][0]) {
+                fpa->toTangentPlane->x->coeff[i][j][0][0] = 0.5*(localX->coeff[i-1][j] / i + localY->coeff[i][j-1] / j);
+            }
+        }
+    }
+    psFree (localX);
+    psFree (localY);
+
+    // the order of the gradient fits need to be 1 less than the distortion term
+    // determine the gradient order(s) from the fpa->toTP structure
+    localX = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, fpa->toTangentPlane->y->nX-1, fpa->toTangentPlane->y->nY);
+    localY = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, fpa->toTangentPlane->y->nX,   fpa->toTangentPlane->y->nY-1);
+
+    // set masks based on fpa->toTP
+    for (int i = 0; i < fpa->toTangentPlane->y->nX; i++) {
+        for (int j = 0; j < fpa->toTangentPlane->y->nY; j++) {
+            if (fpa->toTangentPlane->y->mask[i][j][0][0]) {
+                localX->mask[i-1][j] = 1;
+                localY->mask[i][j-1] = 1;
+            }
+        }
+    }
+
+    // fit the local gradients in each direction
+    psVectorClipFitPolynomial2D (localX, stats, mask, 0xff, dQdL, NULL, L, M);
+    psVectorClipFitPolynomial2D (localY, stats, mask, 0xff, dQdM, NULL, L, M);
+
+    // XXX test: generate fit
+    psVector *dQdLf = psPolynomial2DEvalVector (localX, L, M);
+    psVector *dQdMf = psPolynomial2DEvalVector (localY, L, M);
+
+    FILE *f = fopen ("grads.dat", "w");
+    for (int i = 0; i < dPdLf->n; i++) {
+        fprintf (f, "%f %f  | %f %f  %f %f | %f %f  %f %f\n",
+                 L->data.F32[i], M->data.F32[i],
+                 dPdL->data.F32[i], dPdM->data.F32[i],
+                 dQdL->data.F32[i], dQdM->data.F32[i],
+                 dPdLf->data.F32[i], dPdMf->data.F32[i],
+                 dQdLf->data.F32[i], dQdMf->data.F32[i]);
+    }
+    fclose (f);
+
+    // update fpa->toTP distortion terms
+    fpa->toTangentPlane->y->coeff[0][0][0][0] = 0;
+    for (int i = 1; i < fpa->toTangentPlane->y->nX; i++) {
+        if (!fpa->toTangentPlane->y->mask[i][0][0][0]) {
+            fpa->toTangentPlane->y->coeff[i][0][0][0] = localX->coeff[i-1][0] / i;
+        }
+    }
+    for (int j = 1; j < fpa->toTangentPlane->y->nY; j++) {
+        if (!fpa->toTangentPlane->y->mask[0][j][0][0]) {
+            fpa->toTangentPlane->y->coeff[0][j][0][0] = localY->coeff[0][j-1] / j;
+        }
+    }
+    for (int i = 1; i < fpa->toTangentPlane->y->nX; i++) {
+        for (int j = 1; j < fpa->toTangentPlane->y->nY; j++) {
+            if (!fpa->toTangentPlane->y->mask[i][j][0][0]) {
+                fpa->toTangentPlane->y->coeff[i][j][0][0] = 0.5*(localX->coeff[i-1][j] / i + localY->coeff[i][j-1] / j);
+            }
+        }
+    }
+    psFree (localX);
+    psFree (localY);
+
+    // XXX free unneeded structures
+    psFree (dPdL);
+    psFree (dPdM);
+    psFree (dQdL);
+    psFree (dQdM);
+    psFree (L);
+    psFree (M);
+    psFree (stats);
+    psFree (mask);
+
+    return true;
+}
Index: /tags/RHL_v1_1/psModules/src/astrom/pmAstrometryDistortion.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/astrom/pmAstrometryDistortion.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/astrom/pmAstrometryDistortion.h	(revision 9533)
@@ -0,0 +1,58 @@
+/** @file  pmAstrometryDistortion.h
+*
+*  @brief This file defines the basic types for measuring and fitting the focal-plane distortion.
+*
+*  @ingroup AstroImage
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-05-19 15:39:53 $
+*
+*  Copyright 2006 Institute for Astronomy, University of Hawaii
+*/
+
+#ifndef PM_ASTROMETRY_DISTORTION_H
+#define PM_ASTROMETRY_DISTORTION_H
+
+/* The following data structure carries the information about the residual
+ * gradient of source positions in the tangent plane (pmAstromObj.TP) as a
+ * function of position in the focal plane (pmAstromObj.FP).
+ */
+typedef struct
+{
+    psPlane FP;
+    psPlane dTPdL;
+    psPlane dTPdM;
+}
+pmAstromGradient;
+
+pmAstromGradient *pmAstromGradientAlloc ();
+
+/* The following function determines the position residual, in the tangent
+ * plane, as a function of position in the focal plane, for a collection of raw
+ * measurements and matched reference stars. The configuration data must include
+ * the bin size over which the gradient is measured (keyword: ASTROM.GRAD.BOX).
+ * The function returns an array of pmAstromGradient structures, defined below.
+ */
+psArray *pmAstromMeasureGradients(
+    psArray *gradients,
+    psArray *rawstars,
+    psArray *refstars,
+    psArray *matches,
+    psMetadata *config
+);
+
+/* The gradient set measured above can be fitted with a pair of 2D
+ * polynomials. The resulting fits can then be related back to the implied
+ * polynomials which represent the distortion. The following function performs
+ * the fit and applies the result to the distortion transformation of the
+ * supplied pmFPA structure. The configuration variable supplies the polynomial
+ * order (keyword: ASTROM.DISTORT.ORDER).
+ */
+bool pmAstromFitDistortion(
+    pmFPA *fpa,
+    psArray *gradients,
+    psMetadata *config);
+
+#endif // PM_ASTROMETRY_DISTORTION_H
Index: /tags/RHL_v1_1/psModules/src/astrom/pmAstrometryObjects.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/astrom/pmAstrometryObjects.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/astrom/pmAstrometryObjects.c	(revision 9533)
@@ -0,0 +1,806 @@
+/** @file  pmAstrometryObjects.c
+*
+*  @brief This file defines the basic types for matching objects
+*  based on their astrometry.
+*
+*  @ingroup AstroImage
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.15 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-10-07 03:54:08 $
+*
+*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/******************************************************************************/
+/*  INCLUDE FILES                                                             */
+/******************************************************************************/
+#include <stdio.h>
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <unistd.h>   // for unlink
+#include <pslib.h>
+#include "pmFPA.h"
+#include "pmAstrometryObjects.h"
+
+
+#define PM_ASTROMETRYOBJECTS_DEBUG 1
+/******************************************************************************
+pmAstromObjSortByMag(**a, **b): sort by mag (descending)
+ 
+Is this a private routine?
+Should we do the early asserts?
+ ******************************************************************************/
+int pmAstromObjSortByMag(
+    const void **a,
+    const void **b)
+{
+    if (PM_ASTROMETRYOBJECTS_DEBUG) {
+        PS_ASSERT_PTR_NON_NULL(a, 0);
+        PS_ASSERT_PTR_NON_NULL(*a, 0);
+        PS_ASSERT_PTR_NON_NULL(b, 0);
+        PS_ASSERT_PTR_NON_NULL(*b, 0);
+    }
+
+    pmAstromObj *A = *(pmAstromObj **)a;
+    pmAstromObj *B = *(pmAstromObj **)b;
+
+    psF32 diff = A->Mag - B->Mag;
+    if (diff > FLT_EPSILON) {
+        return (-1);
+    }
+
+    if (diff < FLT_EPSILON) {
+        return (+1);
+    }
+
+    return (0);
+}
+
+/************************************************************************************************************/
+/*
+ * Working routine to match two lists (where x[12] are sorted), given psVectors of their coordinates and the
+ * permutation used to sort in x
+ */
+static psArray *match_lists(const psVector *x1, const psVector *y1, // x/y coordinates of first set of objects
+                            const psVector *x2, const psVector *y2, // x/y   "    "    "  second "   "  "   "
+                            const psVector *sorted1, const psVector *sorted2, // mapping to original order
+                            const double RADIUS) // matching radius
+{
+    psArray *matches = psArrayAlloc(x1->n);
+    matches->n = 0;
+
+    const double RADIUS_SQR = PS_SQR(RADIUS);
+    double dX, dY, dR;
+
+    int jStart;
+    int i = 0, j = 0;
+    while (i < x1->n && j < x2->n) {
+        dX = x1->data.F64[i] - x2->data.F64[j];
+        if (dX <= -RADIUS) {
+            i++;
+            continue;
+        }
+        if (dX >= +RADIUS) {
+            j++;
+            continue;
+        }
+
+        jStart = j;
+        while (fabs(dX) < RADIUS && j < x2->n) {
+
+            dX = x1->data.F64[i] - x2->data.F64[j];
+            dY = y1->data.F64[i] - y2->data.F64[j];
+            dR = dX*dX + dY*dY;
+
+            if (dR > RADIUS_SQR) {
+                j++;
+                continue;
+            }
+
+            // got a match; add to output list
+            pmAstromMatch *match = pmAstromMatchAlloc (sorted1->data.S32[i], sorted2->data.S32[j]);
+            psArrayAdd (matches, 100, match);
+            psFree (match);
+
+            j++;
+        }
+        j = jStart;
+        i++;
+    }
+
+    return (matches);
+}
+
+/************************************************************************************************************/
+
+#define MAKE_ASTROM_RADIUS(FUNC, MEMBER) \
+psArray *FUNC( \
+               const psArray *st1, \
+               const psArray *st2, \
+               const psMetadata *config) \
+{ \
+    PS_ASSERT_PTR_NON_NULL(st1, NULL); \
+    PS_ASSERT_PTR_NON_NULL(st2, NULL); \
+    PS_ASSERT_PTR_NON_NULL(config, NULL); \
+    \
+    assert(st1->n == 0 || pmIsAstromObj(st1->data[0])); \
+    assert(st2->n == 0 || pmIsAstromObj(st2->data[0])); \
+    \
+    bool status = false; \
+    double RADIUS = psMetadataLookupF32 (&status, config, "PSASTRO.MATCH.RADIUS"); \
+    if (!status) { \
+        psError(PS_ERR_IO, false, "Failed to lookup matching radius"); \
+        return NULL; \
+    } \
+    /* sort both lists by X coord; st1 first */ \
+    psVector *x1 = psVectorAlloc(st1->n, PS_TYPE_F64); \
+    \
+    x1->n = x1->nalloc; \
+    for (int i = 0; i < st1->n; i++) { \
+        x1->data.F64[i] = ((pmAstromObj *)st1->data[i])->MEMBER->x; \
+    } \
+    const psVector *sorted1 = psVectorSortIndex(NULL, x1); \
+    assert (sorted1->type.type == PS_TYPE_S32); \
+    \
+    psVector *y1 = psVectorAlloc(st1->n, PS_TYPE_F64); \
+    y1->n = y1->nalloc; \
+    for (int i = 0; i < st1->n; i++) { \
+        x1->data.F64[i] = ((pmAstromObj *)st1->data[sorted1->data.S32[i]])->MEMBER->x; \
+        y1->data.F64[i] = ((pmAstromObj *)st1->data[sorted1->data.S32[i]])->MEMBER->y; \
+    } \
+    \
+    /* now st2 */ \
+    psVector *x2 = psVectorAlloc(st2->n, PS_TYPE_F64); \
+    x2->n = x2->nalloc; \
+    for (int i = 0; i < st2->n; i++) { \
+        x2->data.F64[i] = ((pmAstromObj *)st2->data[i])->MEMBER->x; \
+    } \
+    const psVector *sorted2 = psVectorSortIndex(NULL, x2); \
+    \
+    psVector *y2 = psVectorAlloc(st2->n, PS_TYPE_F64); \
+    y2->n = y2->nalloc; \
+    for (int i = 0; i < st2->n; i++) { \
+        x2->data.F64[i] = ((pmAstromObj *)st2->data[sorted2->data.S32[i]])->MEMBER->x; \
+        y2->data.F64[i] = ((pmAstromObj *)st2->data[sorted2->data.S32[i]])->MEMBER->y; \
+    } \
+    /* Do the work */ \
+    psArray *matches = match_lists(x1, y1, x2, y2, sorted1, sorted2, RADIUS); \
+    \
+    psFree(sorted1); \
+    psFree(sorted2); \
+    psFree(x1); \
+    psFree(y1); \
+    psFree(x2); \
+    psFree(y2); \
+    \
+    psLogMsg (__func__, 3, "radius match: %ld pairs (radius: %f)\n", matches->n, RADIUS); \
+    return (matches); \
+}
+
+/******************************************************************************/
+/*
+ * Match two lists of pmAstromObjs, based on the FP, TP, or chip coordinates
+ */
+MAKE_ASTROM_RADIUS(pmAstromRadiusMatch, FP)
+MAKE_ASTROM_RADIUS(pmAstromRadiusMatchTP, TP)
+MAKE_ASTROM_RADIUS(pmAstromRadiusMatchChip, chip)
+
+/******************************************************************************
+pmAstromMatchFit(map, raw, ref, match, config): take two matched star lists
+and fit a psPlaneTransform between them
+ ******************************************************************************/
+psPlaneTransform *pmAstromMatchFit(
+    psPlaneTransform *map,
+    psArray *raw,
+    psArray *ref,
+    psArray *match,
+    psMetadata *config,
+    psMetadata *updates)
+{
+    PS_ASSERT_PTR_NON_NULL(raw, NULL);
+    PS_ASSERT_PTR_NON_NULL(ref, NULL);
+    PS_ASSERT_PTR_NON_NULL(match, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    bool status;
+    pmAstromObj *rawStar, *refStar;
+    pmAstromMatch *pair;
+
+    if (map == NULL) {
+        int nX = psMetadataLookupS32 (&status, config, "PSASTRO.CHIP.NX");
+        if (!status) {
+            nX = 1;
+        }
+        int nY = psMetadataLookupS32 (&status, config, "PSASTRO.CHIP.NY");
+        if (!status) {
+            nY = 1;
+        }
+        map = psPlaneTransformAlloc (nX, nY);
+        // XXX EAM : not clear that we are allowed to use orders > 1
+    }
+
+    psStats *stats = psStatsAlloc (PS_STAT_CLIPPED_MEAN | PS_STAT_CLIPPED_STDEV);
+    stats->clipSigma = psMetadataLookupF32 (&status, config, "PSASTRO.CHIP.NSIGMA");
+    stats->clipIter  = psMetadataLookupS32 (&status, config, "PSASTRO.CHIP.NITER");
+
+    // XXX EAM : clip fit seems to only work for F32!
+    // XXX EAM : clip fit fails to handle NULL error
+    psVector *X = psVectorAlloc (match->n, PS_TYPE_F32);
+    psVector *Y = psVectorAlloc (match->n, PS_TYPE_F32);
+    psVector *x = psVectorAlloc (match->n, PS_TYPE_F32);
+    psVector *y = psVectorAlloc (match->n, PS_TYPE_F32);
+    psVector *wt = psVectorAlloc (match->n, PS_TYPE_F32);
+    X->n = X->nalloc;
+    Y->n = Y->nalloc;
+    x->n = x->nalloc;
+    y->n = y->nalloc;
+    wt->n = wt->nalloc;
+    // take the matched stars, first fit
+    for (int i = 0; i < match->n; i++) {
+
+        pair    = match->data[i];
+        rawStar = raw->data[pair->raw];
+        refStar = ref->data[pair->ref];
+
+        X->data.F32[i] = rawStar->chip->x;
+        Y->data.F32[i] = rawStar->chip->y;
+
+        x->data.F32[i] = refStar->FP->x;
+        y->data.F32[i] = refStar->FP->y;
+
+        wt->data.F32[i] = 1.0;
+    }
+
+    // constant errors
+    psVector *xMask = psVectorAlloc (match->n, PS_TYPE_U8);
+    psVector *yMask = psVectorAlloc (match->n, PS_TYPE_U8);
+    xMask->n = yMask->n = match->n;
+    psVectorInit (xMask, 0);
+    psVectorInit (yMask, 0);
+
+    double dX, dY;
+
+    // fit chip-to-FPA transformation
+    // XXX should force x->n and y->n to be the same
+    psVectorClipFitPolynomial2D (map->x, stats, xMask, 0, x, wt, X, Y);
+    psLogMsg ("psModules.astrom", 3, "x resid: %f +/- %f (%ld of %ld)\n", stats->clippedMean, stats->clippedStdev, stats->clippedNvalues, x->n);
+    dX = stats->clippedStdev;
+
+    psVectorClipFitPolynomial2D (map->y, stats, yMask, 0, y, wt, X, Y);
+    psLogMsg ("psModules.astrom", 3, "y resid: %f +/- %f (%ld of %ld)\n", stats->clippedMean, stats->clippedStdev, stats->clippedNvalues, y->n);
+    dY = stats->clippedStdev;
+
+    // XXX this is somewhat ad-hoc: we need to be sure these values are in arcsec
+    // and we need to set NASTRO = 0 if we have a failure, etc
+    // XXX this is a somewhat silly place to write these updates...
+    if (updates) {
+        psMetadataAddF32 (updates, PS_LIST_TAIL, "NASTRO",   PS_META_REPLACE, "", PS_MIN(x->n, y->n));
+        psMetadataAddF32 (updates, PS_LIST_TAIL, "CERROR",   PS_META_REPLACE, "", hypot(dX, dY));
+        psMetadataAddF32 (updates, PS_LIST_TAIL, "CPRECISE", PS_META_REPLACE, "", hypot(dX, dY)/sqrt(PS_MIN(x->n, y->n)));
+        psMetadataAddF32 (updates, PS_LIST_TAIL, "EQUINOX",  PS_META_REPLACE, "", 2000.0); // XXX this is bogus: must be defined somewhere
+    }
+
+    psFree (x);
+    psFree (y);
+    psFree (X);
+    psFree (Y);
+    psFree (wt);
+    psFree (stats);
+    psFree (xMask);
+    psFree (yMask);
+
+    return (map);
+}
+
+
+/******************************************************************************
+pmAstromRotateObj(old, center, angle): rotate the focal-plane coordinates
+about the center coordinate angle specified in radians
+ ******************************************************************************/
+psArray *pmAstromRotateObj(
+    const psArray *old,
+    psPlane center,
+    double angle)
+{
+    PS_ASSERT_PTR_NON_NULL(old, NULL);
+
+    double X, Y;
+    pmAstromObj *newObj;
+    const pmAstromObj *oldObj;
+
+    psArray *new = psArrayAlloc (old->n);
+    new->n = new->nalloc;
+    double cs = cos(angle);
+    double sn = sin(angle);
+    double xCenter = center.x;
+    double yCenter = center.y;
+
+    for (int i = 0; i < old->n; i++) {
+
+        oldObj = (pmAstromObj *)old->data[i];
+        newObj = pmAstromObjCopy (oldObj);
+
+        X = oldObj->FP->x - xCenter;
+        Y = oldObj->FP->y - yCenter;
+
+        newObj->FP->x = X*cs + Y*sn + xCenter;
+        newObj->FP->y = Y*cs - X*sn + yCenter;
+
+        new->data[i] = newObj;
+    }
+    return (new);
+}
+
+
+
+/******************************************************************************
+astromObjFree(obj)
+ ******************************************************************************/
+static void astromObjFree(pmAstromObj *obj)
+{
+    if (obj == NULL) {
+        return;
+    }
+
+    psFree(obj->pix);
+    psFree(obj->cell);
+    psFree(obj->chip);
+    psFree(obj->FP);
+    psFree(obj->TP);
+    psFree(obj->sky);
+
+    return;
+}
+
+
+/******************************************************************************
+pmAstromObjAlloc()
+ ******************************************************************************/
+pmAstromObj *pmAstromObjAlloc(void)
+{
+    pmAstromObj *obj = psAlloc (sizeof(pmAstromObj));
+    psMemSetDeallocator (obj, (psFreeFunc)astromObjFree);
+
+    obj->pix  = psPlaneAlloc();
+    obj->cell = psPlaneAlloc();
+    obj->chip = psPlaneAlloc();
+    obj->FP   = psPlaneAlloc();
+    obj->TP   = psPlaneAlloc();
+    obj->sky  = psSphereAlloc();
+    obj->Mag  = 0;
+    obj->dMag = 0;
+
+    return (obj);
+}
+
+bool pmIsAstromObj(const psPtr ptr)
+{
+    return (psMemGetDeallocator(ptr) == (psFreeFunc)astromObjFree);
+}
+
+
+
+/******************************************************************************
+pmAstromObjCopy(old)
+ ******************************************************************************/
+pmAstromObj *pmAstromObjCopy(const pmAstromObj *old)
+{
+    PS_ASSERT_PTR_NON_NULL(old, NULL);
+    pmAstromObj *obj = pmAstromObjAlloc();
+
+    *obj->pix  = *old->pix;
+    *obj->cell = *old->cell;
+    *obj->chip = *old->chip;
+    *obj->FP   = *old->FP;
+    *obj->TP   = *old->TP;
+    *obj->sky  = *old->sky;
+
+    return(obj);
+}
+
+
+/******************************************************************************
+ ******************************************************************************/
+static void pmAstromMatchFree (pmAstromMatch *match)
+{
+    if (match == NULL)
+        return;
+    return;
+}
+
+
+/******************************************************************************
+ ******************************************************************************/
+pmAstromMatch *pmAstromMatchAlloc(
+    int raw,
+    int ref)
+{
+    pmAstromMatch *match = psAlloc (sizeof(pmAstromMatch));
+    psMemSetDeallocator(match, (psFreeFunc) pmAstromMatchFree);
+
+    match->raw = raw;
+    match->ref = ref;
+
+    return (match);
+}
+
+
+static double maxOffpix;                // maximum allowed offset between lists, in raw pixels
+static double Scale;                    // grid pixel scale static
+double Offset;                          // deltas to pixels
+/******************************************************************************
+AstromGridBin(*dx, *dy, dX, dY): local function to convert x,y coords to grid
+bins it requires the globals defined above.
+ 
+ ******************************************************************************/
+static bool AstromGridBin(
+    int *dx,
+    int *dy,
+    double dX,
+    double dY)
+{
+    if (PM_ASTROMETRYOBJECTS_DEBUG) {
+        PS_ASSERT_PTR_NON_NULL(dx, false);
+        PS_ASSERT_PTR_NON_NULL(dy, false);
+    }
+
+    if (fabs(dX) > maxOffpix) {
+        return false;
+    }
+
+    if (fabs(dY) > maxOffpix) {
+        return false;
+    }
+
+    *dx = dX / Scale + Offset;
+    *dy = dY / Scale + Offset;
+    return true;
+}
+
+
+/******************************************************************************
+pmAstromGridAngle(raw, ref, config): match the two lists using the binned
+delta-delta max.
+ ******************************************************************************/
+pmAstromStats pmAstromGridAngle(
+    const psArray *raw,
+    const psArray *ref,
+    const psMetadata *config)
+{
+    // XXX: What to do if input parameters are bad?
+    pmAstromStats badStat;
+    PS_ASSERT_PTR_NON_NULL(raw, badStat);
+    PS_ASSERT_PTR_NON_NULL(ref, badStat);
+    PS_ASSERT_PTR_NON_NULL(config, badStat);
+
+    bool status;
+    int nPix;       // size of matching grid
+    int nPixHalf;   // half-size of matching grid
+    double dX, dY;  // offset between a possible matched pair
+    int iX, iY;     // corresponding grid bin
+
+    const pmAstromObj *ob1, *ob2; // short-cut pointers to the objects
+    pmAstromStats stats;    // output match statistics
+
+    // max allowed offset in either X or Y directions
+    double gridOffset = psMetadataLookupF32 (&status, config, "PSASTRO.GRID.OFFSET");
+
+    // sampling scale of the grid
+    double gridScale  = psMetadataLookupF32 (&status, config, "PSASTRO.GRID.SCALE");
+
+    // set the static scaling factors
+    nPixHalf = (int)(gridOffset / gridScale + 0.5);  // half-grid
+    nPix = 2*nPixHalf + 1;                           // full grid width
+
+    // these are globals used by p_pmAstromGridBin
+    maxOffpix = gridScale * (nPixHalf + 0.5);            // max offset from true center
+    Offset    = maxOffpix / gridScale;
+    Scale     = gridScale;
+
+    // images used as accumulators for the loop below
+    psImage *gridNP = psImageAlloc (nPix, nPix, PS_TYPE_U32);
+    psImage *gridDX = psImageAlloc (nPix, nPix, PS_TYPE_F32);
+    psImage *gridDY = psImageAlloc (nPix, nPix, PS_TYPE_F32);
+    psImage *gridD2 = psImageAlloc (nPix, nPix, PS_TYPE_F32);
+    psImageInit (gridNP, 0);
+    psImageInit (gridDX, 0);
+    psImageInit (gridDY, 0);
+    psImageInit (gridD2, 0);
+
+    // short-cut names for grid images
+    psS32 **NP = gridNP->data.S32;
+    psF32 **DX = gridDX->data.F32;
+    psF32 **DY = gridDY->data.F32;
+    psF32 **D2 = gridD2->data.F32;
+
+    // accumulate grids for focal plane (L,M) matches
+    for (int i = 0; i < raw->n; i++) {
+        ob1 = (pmAstromObj *)raw->data[i];
+        for (int j = 0; j < ref->n; j++) {
+            ob2 = (pmAstromObj *)ref->data[j];
+            dX = ob1->FP->x - ob2->FP->x;
+            dY = ob1->FP->y - ob2->FP->y;
+
+            // fprintf (stderr, "dX,dY: %8.2f %8.2f : %8.2f %8.2f : %8.2f %8.2f\n", dX, dY, ob1->FP->x, ob2->FP->x, ob1->FP->y, ob2->FP->y);
+            // find bin coordinates for this delta-delta
+            if (!AstromGridBin (&iX, &iY, dX, dY)) {
+                continue; // matched pair is too far offset
+            }
+
+            // accumulate bin stats
+            NP[iY][iX] ++;
+            DX[iY][iX] += dX;
+            DY[iY][iX] += dY;
+            D2[iY][iX] += PS_SQR(dX) + PS_SQR(dY);
+        }
+    }
+
+    // now assess the grid images
+    {
+        double minMetric = 1e10;
+        double minVar = 1e10;
+        int minX = -1;
+        int minY = -1;
+        double metric, var;
+
+        // find the max pixel
+        psStats *imStats = psStatsAlloc (PS_STAT_MAX);
+        imStats = psImageStats (imStats, gridNP, NULL, 0);
+
+        // only check bins with at least 1/2 of max bin
+        int minNpts = 0.5*imStats->max;
+        psTrace("psModule.astrom.grid.angle", 5, "minNpts: %d, max: %d", minNpts, (int)(imStats->max));
+
+        // find the 'best' bin
+        for (int j = 0; j < gridNP->numRows; j++)
+        {
+            for (int i = 0; i < gridNP->numCols; i++) {
+
+                if (NP[j][i] < minNpts)
+                    continue;
+
+                // this metric emphasizes a narrow peak with lots of sources over one with few.
+                var = fabs((D2[j][i]/NP[j][i]) - PS_SQR(DX[j][i]/NP[j][i]) - PS_SQR(DY[j][i]/NP[j][i]));
+                metric = var / PS_SQR(NP[j][i]) / PS_SQR(NP[j][i]);
+
+                // fprintf (stderr, "try : %f %f (%d pts, %f var, %f met)\n", DX[j][i]/NP[j][i], DY[j][i]/NP[j][i], NP[j][i], var, metric);
+
+                if (metric < minMetric) {
+                    minMetric = metric;
+                    minVar    = var;
+                    minX      = i;
+                    minY      = j;
+                }
+            }
+        }
+
+        // convert the bin to delta-delta
+        stats.offset.x  = DX[minY][minX] / NP[minY][minX];
+        stats.offset.y  = DY[minY][minX] / NP[minY][minX];
+        stats.minMetric = minMetric;
+        stats.minVar    = minVar;
+        stats.nMatch    = NP[minY][minX];
+
+        psFree (imStats);
+        // XXX EAM : This routine, and pmAstromGridMatch, need to handle failure cases better
+    }
+    psFree (gridNP);
+    psFree (gridDX);
+    psFree (gridDY);
+    psFree (gridD2);
+    return (stats);
+}
+
+
+
+/******************************************************************************
+pmAstromGridMatch(*raw, *ref, *config): match two star lists.
+ ******************************************************************************/
+
+pmAstromStats pmAstromGridMatch(const psArray *raw,
+                                const psArray *ref,
+                                const psMetadata *config)
+{
+    // XXX: What to do if input parameters are bad?
+    pmAstromStats badStat;
+    PS_ASSERT_PTR_NON_NULL(raw, badStat);
+    PS_ASSERT_PTR_NON_NULL(ref, badStat);
+    PS_ASSERT_PTR_NON_NULL(config, badStat);
+
+    bool status;
+    double xMin, xMax, yMin, yMax;
+    const pmAstromObj *obj;
+    psArray *rot;
+
+    pmAstromStats minStat;
+    pmAstromStats newStat = {{0, 0, 0, 0}, {0, 0, 0, 0}, 0, 0, 0, 0};
+    psPlane center;
+
+    // find center of the raw field (focal-plane coords)
+    xMin = yMin = +1e10;
+    xMax = yMax = -1e10;
+    for (int i = 0; i < raw->n; i++) {
+        obj = (pmAstromObj *)raw->data[i];
+        xMin = PS_MIN (obj->FP->x, xMin);
+        xMax = PS_MAX (obj->FP->x, xMax);
+        yMin = PS_MIN (obj->FP->y, yMin);
+        yMax = PS_MAX (obj->FP->y, yMax);
+    }
+    center.x = 0.5*(xMin + xMax);
+    center.y = 0.5*(yMin + yMax);
+
+    double minAngle = psMetadataLookupF32 (&status, config, "PSASTRO.GRID.MIN.ANGLE");
+    double maxAngle = psMetadataLookupF32 (&status, config, "PSASTRO.GRID.MAX.ANGLE");
+    double delAngle = psMetadataLookupF32 (&status, config, "PSASTRO.GRID.DEL.ANGLE");
+
+    minStat.minMetric = 1e10;
+    for (double angle = minAngle; angle <= maxAngle; angle += delAngle) {
+        rot = pmAstromRotateObj (raw, center, angle);
+        newStat = pmAstromGridAngle (rot, ref, config);
+        psTrace("psModule.astrom.grid.match", 5, "try %f : %f %f (%d pts, %f var, %f met (%f log))",
+                angle, newStat.offset.x, newStat.offset.y, newStat.nMatch, newStat.minVar, newStat.minMetric,
+                log10(newStat.minMetric));
+
+        newStat.angle  = angle;
+        newStat.center = center;
+        if (newStat.minMetric < minStat.minMetric) {
+            minStat = newStat;
+            psTrace("psModule.astrom.grid.match", 5, "use %f : %f %f (%d pts, %f var, %f met (%f log))",
+                    angle, minStat.offset.x, minStat.offset.y, minStat.nMatch, minStat.minVar, minStat.
+                    minMetric, log10(minStat.minMetric));
+        }
+        psFree (rot);
+    }
+    psTrace("psModule.astrom.grid.match", 4, "best: %f %f (%d pts, %f var, %f met)",
+            minStat.offset.x, minStat.offset.y, minStat.nMatch, minStat.minVar, log10(minStat.minMetric));
+    return (minStat);
+}
+
+/******************************************************************************
+pmAstromGridTweak(*raw, *ref, *recipe, stats): improve match for two star lists.
+ ******************************************************************************/
+pmAstromStats pmAstromGridTweak(
+    psArray *raw,
+    psArray *ref,
+    psMetadata *recipe,
+    pmAstromStats stats)
+{
+    bool status;
+    pmAstromObj *ob1, *ob2;  // short-cut pointers to the objects
+    double dX, dY;   // offset between a possible matched pair
+    psArray *rot;
+    int nBin, xBin, yBin;
+
+    rot = pmAstromRotateObj (raw, stats.center, stats.angle);
+
+    // sampling scale of the grid
+    double tweakScale  = psMetadataLookupF32 (&status, recipe, "PSASTRO.TWEAK.SCALE");
+    double tweakRange  = psMetadataLookupF32 (&status, recipe, "PSASTRO.TWEAK.RANGE");
+    double tweakSmooth = psMetadataLookupF32 (&status, recipe, "PSASTRO.TWEAK.SMOOTH");
+    double tweakNsigma = psMetadataLookupF32 (&status, recipe, "PSASTRO.TWEAK.NSIGMA");
+
+    nBin = 2*tweakRange / tweakScale;
+    psVector *xHist = psVectorAlloc (nBin, PS_TYPE_F32);
+    psVector *yHist = psVectorAlloc (nBin, PS_TYPE_F32);
+    xHist->n = yHist->n = nBin;
+    psVectorInit (xHist, 0);
+    psVectorInit (yHist, 0);
+
+    // accumulate grids for focal plane (L,M) matches
+    for (int i = 0; i < rot->n; i++) {
+        ob1 = (pmAstromObj *)rot->data[i];
+        for (int j = 0; j < ref->n; j++) {
+            ob2 = (pmAstromObj *)ref->data[j];
+            dX = ob1->FP->x - ob2->FP->x - stats.offset.x;
+            dY = ob1->FP->y - ob2->FP->y - stats.offset.y;
+
+            xBin = (dX + tweakRange) / tweakScale;
+            yBin = (dY + tweakRange) / tweakScale;
+
+            if (xBin < 0)
+                continue;
+            if (yBin < 0)
+                continue;
+            if (xBin >= nBin)
+                continue;
+            if (yBin >= nBin)
+                continue;
+
+            xHist->data.F32[xBin] += 1.0;
+            yHist->data.F32[yBin] += 1.0;
+        }
+    }
+
+    // smooth histgram vector with gaussian of 1sigma = radius
+    psVectorSmooth(xHist, xHist, tweakSmooth, tweakNsigma);
+    psVectorSmooth(yHist, yHist, tweakSmooth, tweakNsigma);
+
+    // select peak in x and in y
+    xBin = yBin = 0;
+    double xMax = 0;
+    double yMax = 0;
+    for (int i = 0; i < nBin; i++) {
+        if (xHist->data.F32[i] > xMax) {
+            xBin = i;
+            xMax = xHist->data.F32[i];
+        }
+        if (yHist->data.F32[i] > yMax) {
+            yBin = i;
+            yMax = yHist->data.F32[i];
+        }
+    }
+    double xPeak = xBin*tweakScale - tweakRange;
+    double yPeak = yBin*tweakScale - tweakRange;
+    psLogMsg (__func__, 3, "tweak peak by %f,%f\n", xPeak, yPeak);
+
+    // adjust offset by peak center
+    pmAstromStats tweak = stats;
+    tweak.offset.x += xPeak;
+    tweak.offset.y += yPeak;
+
+    psFree (rot);
+    psFree (xHist);
+    psFree (yHist);
+
+    return tweak;
+}
+
+/******************************************************************************
+pmAstromGridApply(*map, stat): apply the measured FPA offset and rotation
+(stat) to the fpa astrom structures.
+ ******************************************************************************/
+psPlaneTransform *pmAstromGridApply(
+    psPlaneTransform *map,
+    pmAstromStats stat)
+{
+    PS_ASSERT_PTR_NON_NULL(map, NULL);
+    PS_ASSERT_POLY_NON_NULL(map->x, NULL);
+    PS_ASSERT_POLY_NON_NULL(map->y, NULL);
+
+    double cs = cos (stat.angle);
+    double sn = sin (stat.angle);
+
+    double dx = (map->x->coeff[0][0] - stat.center.x);
+    double dy = (map->y->coeff[0][0] - stat.center.y);
+
+    // new offset
+    map->x->coeff[0][0] =  cs*dx + sn*dy - stat.offset.x + stat.center.x;
+    map->y->coeff[0][0] = -sn*dx + cs*dy - stat.offset.y + stat.center.y;
+
+    // original rotation matrix
+    double pc1_1 = map->x->coeff[1][0];
+    double pc1_2 = map->x->coeff[0][1];
+    double pc2_1 = map->y->coeff[1][0];
+    double pc2_2 = map->y->coeff[0][1];
+
+    // new rotation matrix
+    map->x->coeff[1][0] = +cs*pc1_1 + sn*pc2_1;
+    map->x->coeff[0][1] = +cs*pc1_2 + sn*pc2_2;
+    map->y->coeff[1][0] = -sn*pc1_1 + cs*pc2_1;
+    map->y->coeff[0][1] = -sn*pc1_2 + cs*pc2_2;
+
+    return (map);
+}
+
+/* Illustration of the grid bins
+   dX        bin
+   -35:-25 -> 0     bin = dX / Scale + Offset
+   -25:-15 -> 1     Scale = 10
+   -15:-05 -> 2     Offset = 3.5
+   -05:+05 -> 3     nPix = 3 (maxOffset = 35 = (nPix + 0.5)*dXix
+   +05:+15 -> 4     dPix = 10
+   +15:+25 -> 5
+   +25:+35 -> 6
+ 
+   maxOffsetRequest = 30
+   nPix = (int) (maxOffset / dPix + 0.5);
+   maxOffset = (nPix + 0.5)*Scale;
+*/
+
Index: /tags/RHL_v1_1/psModules/src/astrom/pmAstrometryObjects.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/astrom/pmAstrometryObjects.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/astrom/pmAstrometryObjects.h	(revision 9533)
@@ -0,0 +1,332 @@
+/** @file  pmAstrometryObjects.h
+*
+*  @brief This file defines the basic types for matching objects
+*  based on their astrometry.
+*
+*  @ingroup AstroImage
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.11 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-10-07 03:54:08 $
+*
+*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+*/
+
+#ifndef PM_ASTROMETRY_OBJECTS_H
+#define PM_ASTROMETRY_OBJECTS_H
+
+/*
+ *
+ * This structure specifies the coordinate of the detection in each of the
+ * four necessary coordinate frames: pix defines the position in the psReadout
+ * frame, FP defines the position in the Focal Plane frame, TP defines the
+ * position in the Tangent Plane frame, sky defines the position on the Celestial
+ * Sphere. In addition, a measurement of the brightness is given by the element
+ * Mag. Such a data structure should be used for both the raw and the reference
+ * stars. In astrometric processing, the raw detections will be projected using
+ * the best available information to each of these coordinate frames from the pix
+ * coordinates, while the reference detections will be projected to the other
+ * frames from the sky coordinates.
+ *
+ * XXX: There are more members here than in the SDRS.
+ *
+ */
+typedef struct
+{
+    psPlane *pix;   ///< the position in the pmReadout frame
+    psPlane *cell;   ///< the position in the pmCell frame
+    psPlane *chip;   ///< the position in the pmChip frame
+    psPlane *FP;   ///< the position in the pmFPA frame
+    psPlane *TP;   ///< the position in the tangent plane
+    psSphere *sky;        ///< the position on the Celestial Sphere.
+    double Mag;                         ///< object magnitude XXX what filter?
+    double dMag;                        ///< error on object magnitude
+}
+pmAstromObj;
+
+/*
+ *
+ * The pmAstromMatch structure defines the cross-correlation between two
+ * arrays. A single such data item specifies that item number pmAstromMatch.idx1
+ * in the first list corresponds to pmAstromMatch.idx2 in the second list.
+ *
+ */
+typedef struct
+{
+    int raw;                             ///< What is this?
+    int ref;                             ///< What is this?
+}
+pmAstromMatch;
+
+
+/*
+ *
+ * XXX: Not in SDRS.
+ *
+ */
+typedef struct
+{
+    psPlane center;                     ///<
+    psPlane offset;                     ///<
+    double  angle;                      ///<
+    double  minMetric;                  ///<
+    double  minVar;                     ///<
+    int     nMatch;                     ///<
+}
+pmAstromStats;
+
+
+
+/*
+ *
+ * If the two sets of coordinates are expected to agree very well (ie, the
+ * current best-guess astrometric solution is quite close to the radius. The
+ * following function accepts two sets of pmAstromObj sources and determines the
+ * matched objects between the two lists. The input sources must have been
+ * projected to the Focal Plane coordinates (pmAstromObj.FP), and the supplied
+ * options entry must contain the desired match radius (keyword:
+ * ASTROM.MATCH.RADIUS). The output consists an array of pmAstromMatch values,
+ * defined below.
+ *
+ */
+psArray *pmAstromRadiusMatch(
+    const psArray *st1,
+    const psArray *st2,
+    const psMetadata *config
+);
+psArray *pmAstromRadiusMatchTP(
+    const psArray *st1,
+    const psArray *st2,
+    const psMetadata *config
+);
+psArray *pmAstromRadiusMatchChip(
+    const psArray *st1,
+    const psArray *st2,
+    const psMetadata *config
+);
+
+
+
+/*
+ *
+ * This function accepts an array of pmAstromObj objects and rotates them by
+ * the given angle about the given center coordinate pCenter,qCenter in the Focal
+ * Plane Array coordinates.
+ *
+ * XXX: This differs from the SDRS
+ *
+ */
+/* SDRS
+psArray *pmAstromRotateObj(
+    psArray *old,
+    double angle,
+    double pCenter,
+    double qCenter
+);
+*/
+psArray *pmAstromRotateObj(
+    const psArray *old,
+    psPlane center,
+    double angle
+);
+
+
+/*
+ *
+ * If the two sets of coordinates are not known to agree well, but the
+ * relative scale and approximate relative rotation is known, then a much faster
+ * match can be found using pair-pair displacements. In such a case, the two
+ * lists can be considered as having the same coordinate system, with an unknown
+ * relative displacement. In this algorithm, all possible pair-wise differences
+ * between the source positions in the two lists are constructed and accumulated
+ * in a grid of possible offset values. The resulting grid is searched for a
+ * cluster representing the offset between the two input lists. This algorithm
+ * can only tolerate a small error in the relative scale or the relative rotation
+ * of the two coordinate lists. However, this process is naturally O(N2), and is
+ * thus advantageous over triangle matching in some circumstances. This process
+ * can be extended to allow a larger uncertainty in the relative rotation by
+ * allowing the procedure to scan over a range of rotations. We define the
+ * following function to apply this matching algorithm:
+ *
+ * XXX: In the SDRS, this function is a pointer.
+ *
+ */
+pmAstromStats pmAstromGridMatch(
+    const psArray *st1,
+    const psArray *st2,
+    const psMetadata *config
+);
+
+/******************************************************************************
+pmAstromGridTweak(*raw, *ref, *recipe, stats): improve match for two star lists.
+ ******************************************************************************/
+pmAstromStats pmAstromGridTweak(
+    psArray *raw,
+    psArray *ref,
+    psMetadata *recipe,
+    pmAstromStats stats);
+
+/*
+ *
+ * The result of a pmAstromGridMatch may be used to modify the astrometry
+ * transformation information for a pmFPA image hierarchy structure. The result
+ * of pmAstromGridMatch defines the adjustments which should be made to the
+ * reference coordinate of the projection (pmFPA.projection.R,D) and the
+ * effective rotation of the Focal Plane.  The rotation implies modification of
+ * the linear terms of the pmFPA.toTangentPlane transformation. These two
+ * adjustments are made using the function:
+ *
+ * XXX: This function name is different in the SDRS.
+ *
+ */
+psPlaneTransform *pmAstromGridApply(
+    psPlaneTransform *map,
+    pmAstromStats stat
+);
+
+
+/*
+ *
+ * This function is identical to pmAstromGridMatch, but is valid for only a
+ * single relative rotation. The input config information need not contain any of
+ * the GRID.*.ANGLE entries (they will be ignored).
+ *
+ * XXX: This function name is different in the SDRS.
+ *
+ */
+/* in pmAstromGrid.c */
+pmAstromStats pmAstromGridAngle(
+    const psArray *st1,
+    const psArray *st2,
+    const psMetadata *config);
+
+
+
+/*
+ *
+ * This function accepts the raw and reference source lists and the list of
+ * matched entries. It uses the matched list to determine a polynomial
+ * transformation between the two coordinate systems. The fitting uses clipping
+ * to exclude outliers, likely representing poor matches. The config element must
+ * contain the information ASTROM.NSIGMA (specifying the number of sigma used in
+ * the clipping) and ASTROM.NCLIP (specifying the number of clipping iterations
+ * must be performed). The config element must also specify the order of the
+ * polynomial fit (keyword: ASTROM.ORDER). The result of this fit is a set of
+ * modifications of the components of the pmFPA.toTangentPlane transformation,
+ * and the modifications of the reference coordinate of the projection
+ * (pmFPA.projection.R,D) and the projection scale (pmFPA.projection.Xs,Ys). The
+ * modifications to pmFPA.toTangentPlane incorporate the rotation component of
+ * the linear terms and the higher-order terms of the polynomial fits.
+ *
+ * XXX: No prototype code.
+ *
+ */
+bool pmAstromFitFPA(
+    pmFPA *fpa,
+    psArray *st1,
+    psArray *st2,
+    psArray *match,
+    psMetadata *config
+);
+
+
+
+/*
+ *
+ * This function accepts the raw and reference source lists for a single chip
+ * and the list of matched entries. It uses the matched list to determine a
+ * polynomial transformation between the two coordinate systems. The fitting
+ * uses clipping to exclude outliers, likely representing poor matches. The
+ * config element must contain the information ASTROM.NSIGMA
+ *(specifying the number of sigma used in the clipping) and ASTROM.NCLIP
+ *(specifying the number of clipping iterations must be performed). The config
+ *element must also specify the order of the polynomial fit (keyword:
+ *ASTROM.ORDER).  The result of this fit is a set of modifications of the
+ *components of the pmChip.toFPA transformation.
+ *
+ * XXX: No prototype code.
+ *
+ */
+bool pmAstromFitChip(
+    pmFPA *fpa,
+    psArray *st1,
+    psArray *st2,
+    psArray *match,
+    psMetadata *config
+);
+
+
+/*******************************************************************************
+ The following functions and structs were in the prototype code, but not the
+ SDRS.
+ ******************************************************************************/
+/*
+ *
+ *
+ *
+ *
+ */
+
+
+/*
+ *
+ * Allocates a pmAstromObj struct.
+ *
+ */
+pmAstromObj *pmAstromObjAlloc (void);
+/*
+ * Is a given pointer a pmAstromObj?
+ */
+bool pmIsAstromObj(const psPtr ptr);
+
+
+/*
+ *
+ * Copies a pmAstromObj struct.
+ *
+ */
+pmAstromObj *pmAstromObjCopy(
+    const pmAstromObj *old
+);
+
+
+
+/*
+ *
+ *
+ *
+ */
+pmAstromMatch *pmAstromMatchAlloc(
+    int i1,
+    int i2
+);
+
+
+
+
+/*
+ *
+ *
+ *
+ */
+psPlaneTransform *pmAstromMatchFit(
+    psPlaneTransform *map,
+    psArray *st1,
+    psArray *st2,
+    psArray *match,
+    psMetadata *config,
+    psMetadata *updates
+);
+
+/*
+ *
+ *
+ *
+ */
+int pmAstromObjSortByMag(
+    const void **a,
+    const void **b
+);
+
+#endif // PM_ASTROMETRY_OBJECTS_H
Index: /tags/RHL_v1_1/psModules/src/astrom/pmFPAAstrometry.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/astrom/pmFPAAstrometry.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/astrom/pmFPAAstrometry.c	(revision 9533)
@@ -0,0 +1,516 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include "pmFPAAstrometry.h"
+#include "pmFPA.h"
+
+
+/*****************************************************************************
+checkValidImageCoords(): this is a private function which simply determines if
+the supplied x,y coordinates are in the range for the supplied psImage.
+ 
+XXX: What about col0 and row0
+XXX: This should return a psBool.
+XXX: Macro this for speed.
+ *****************************************************************************/
+static psS32 checkValidImageCoords(
+    double x,
+    double y,
+    psImage* tmpImage)
+{
+    PS_ASSERT_IMAGE_NON_NULL(tmpImage, 0);
+
+    // The FLT_EPSILON is because -0.0 was failing this.
+    if (((x+FLT_EPSILON) < 0.0) || (x > (double)tmpImage->numCols) ||
+            ((y+FLT_EPSILON) < 0.0) || (y > (double)tmpImage->numRows)) {
+        return (0);
+    }
+
+    return (1);
+}
+
+/*****************************************************************************/
+/* FUNCTION IMPLEMENTATION - PUBLIC                                          */
+/*****************************************************************************/
+
+pmCell* pmCellInFPA(
+    const psPlane* fpaCoord,
+    const pmFPA* FPA)
+{
+    PS_ASSERT_PTR_NON_NULL(fpaCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(FPA, NULL);
+
+    pmChip* tmpChip = NULL;
+    psPlane chipCoord;
+    pmCell* outCell = NULL;
+
+    // Determine which chip contains the fpaCoords.
+    tmpChip = pmChipInFPA(fpaCoord, FPA);
+    if (tmpChip == NULL) {
+        return(NULL);
+    }
+
+    // Convert to those chip coordinates.
+    psPlane *rc = pmCoordFPAToChip(&chipCoord, fpaCoord, tmpChip);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine Chip coords.\n");
+        return(NULL);
+    }
+
+    // Determine which cell contains those chip coordinates.
+    outCell = pmCellInChip(&chipCoord, tmpChip);
+    if (outCell == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine the cell.\n");
+        return(NULL);
+    }
+
+    return (outCell);
+}
+
+pmChip* pmChipInFPA(
+    const psPlane* fpaCoord,
+    const pmFPA* FPA)
+{
+    PS_ASSERT_PTR_NON_NULL(fpaCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(FPA, NULL);
+    PS_ASSERT_PTR_NON_NULL(FPA->chips, NULL);
+
+    psArray* chips = FPA->chips;
+    psS32 nChips = chips->n;
+    psPlane chipCoord;
+    pmCell *tmpCell = NULL;
+
+    //
+    // Loop through every chip in this FPA.  Convert the original FPA
+    // coordinates to chip coordinates for that chip.  Then, determine if any
+    // cells in that chip contain those chip coordinates.
+    // XXX: Depending on the number of chips, and their topology, there may be
+    // a much more efficient way of doing this.
+    //
+    for (psS32 i = 0; i < nChips; i++) {
+        pmChip* tmpChip = chips->data[i];
+        PS_ASSERT_PTR_NON_NULL(tmpChip, NULL);
+        PS_ASSERT_PTR_NON_NULL(tmpChip->fromFPA, NULL);
+
+        psPlaneTransformApply(&chipCoord, tmpChip->fromFPA, fpaCoord);
+
+        tmpCell = pmCellInChip(&chipCoord, tmpChip);
+        if (tmpCell != NULL) {
+            return(tmpChip);
+        }
+    }
+
+    psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine the chip.\n");
+    return (NULL);
+}
+
+
+pmCell* pmCellInChip(
+    const psPlane* chipCoord,
+    const pmChip* chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chipCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    psPlane cellCoord;
+    psArray* cells;
+
+    cells = chip->cells;
+    if (cells == NULL) {
+        return NULL;
+    }
+
+    //
+    // We loop over each cell in the chip.  We transform the chipCoord into
+    // a cellCoord for that cell and determine if that cellCoord is valid.
+    // If so, then we return that cell.
+    // XXX: Depending on the number of cells, and their topology, there may be
+    // a much more efficient way of doing this.
+    //
+    for (psS32 i = 0; i < cells->n; i++) {
+        pmCell* tmpCell = (pmCell* ) cells->data[i];
+        PS_ASSERT_PTR_NON_NULL(tmpCell, NULL);
+
+        psPlaneTransform *chipToCell = NULL;
+        if (true ==  p_psIsProjectionLinear(tmpCell->toChip)) {
+            chipToCell = p_psPlaneTransformLinearInvert(tmpCell->toChip);
+        } else {
+            psLogMsg(__func__, PS_LOG_WARN, "WARNING: non-linear cell->chip transforms are not yet implemented.\n");
+            //chipToCell = psPlaneTransformInvert(NULL, tmpCell->toChip, NULL, -1);
+            chipToCell = NULL;
+        }
+        if (chipToCell == NULL) {
+            psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not invert the Cell->toChip transform.\n");
+            return(NULL);
+        }
+        psArray* readouts = tmpCell->readouts;
+
+        if (readouts != NULL) {
+            for (psS32 j = 0; j < readouts->n; j++) {
+                pmReadout* tmpReadout = readouts->data[j];
+                PS_ASSERT_READOUT_NON_NULL(tmpReadout, NULL);
+
+                psPlaneTransformApply(&cellCoord,
+                                      chipToCell,
+                                      chipCoord);
+
+                if (checkValidImageCoords(cellCoord.x,
+                                          cellCoord.y,
+                                          tmpReadout->image)) {
+                    psFree(chipToCell);
+                    return (tmpCell);
+                }
+            }
+        }
+        psFree(chipToCell);
+    }
+
+    //psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine the cell.\n");
+    return (NULL);
+}
+
+
+psPlane* pmCoordCellToFPA(
+    psPlane* fpaCoord,
+    const psPlane* cellCoord,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cellCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+
+    psPlane *rc = psPlaneTransformApply(fpaCoord, cell->toFPA, cellCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform cell coords to FPA coords.\n");
+    }
+    return(rc);
+}
+
+
+psPlane* pmCoordChipToFPA(
+    psPlane* outCoord,
+    const psPlane* inCoord,
+    const pmChip* chip)
+{
+    PS_ASSERT_PTR_NON_NULL(inCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    psPlane *rc = psPlaneTransformApply(outCoord, chip->toFPA, inCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform chip coords to FPA coords.\n");
+    }
+    return(rc);
+}
+
+
+psPlane* pmCoordFPAToChip(
+    psPlane* chipCoord,
+    const psPlane* fpaCoord,
+    const pmChip* chip)
+{
+    PS_ASSERT_PTR_NON_NULL(fpaCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+    PS_ASSERT_PTR_NON_NULL(chip->fromFPA, NULL);
+
+    psPlane *rc = psPlaneTransformApply(chipCoord, chip->fromFPA, fpaCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform FPA coords to Chip coords.\n");
+    }
+    return(rc);
+}
+
+psPlane* pmCoordCellToChip(
+    psPlane* outCoord,
+    const psPlane* inCoord,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(inCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+
+    psPlane *rc = psPlaneTransformApply(outCoord, cell->toChip, inCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform Cell coords to Chip coords.\n");
+    }
+    return(rc);
+}
+
+psPlane* pmCoordChipToCell(
+    psPlane* cellCoord,
+    const psPlane* chipCoord,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(chipCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);
+
+    pmCell *tmpCell = pmCellInChip(chipCoord, cell->parent);
+    if (tmpCell == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine the proper cell.\n");
+        return(NULL);
+    }
+
+    psPlaneTransform *tmpChipToCell = NULL;
+    PS_ASSERT_PTR_NON_NULL(tmpCell->toChip, NULL);
+    if (true ==  p_psIsProjectionLinear(tmpCell->toChip)) {
+        tmpChipToCell = p_psPlaneTransformLinearInvert(tmpCell->toChip);
+    } else {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: non-linear cell->chip transforms are not yet implemented.\n");
+        // XXX: tmpChipToCell = psPlaneTransformInvert(NULL, tmpCell->toChip, NULL, -1);
+        tmpChipToCell = NULL;
+    }
+    if (tmpChipToCell == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not invert the Cell->toChip transform.\n");
+        return(NULL);
+    }
+
+    psPlane *rc = psPlaneTransformApply(cellCoord, tmpChipToCell, chipCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform Chip coords to Cell coords.\n");
+    }
+    psFree(tmpChipToCell);
+    return(rc);
+}
+
+psPlane* pmCoordFPAToTP(
+    psPlane* outCoord,
+    const psPlane* inCoord,
+    double color,
+    double magnitude,
+    const pmFPA* fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(inCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    psPlane *rc = psPlaneDistortApply(outCoord, fpa->toTangentPlane, inCoord, color, magnitude);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform FPA coords to tangent plane coords.\n");
+    }
+    return(rc);
+}
+
+psPlane* pmCoordTPToFPA(
+    psPlane* fpaCoord,
+    const psPlane* tpCoord,
+    double color,
+    double magnitude,
+    const pmFPA* fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(tpCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa->fromTangentPlane, NULL);
+
+    psPlane *rc = psPlaneDistortApply(fpaCoord, fpa->fromTangentPlane, tpCoord, color, magnitude);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform tangent plane coords to FPA coords.\n");
+    }
+    return(rc);
+}
+
+
+/*****************************************************************************
+XXXDeproject(outSphere, coord, projection): This private routine is a wrapper
+for p_psDeproject().  The reason: p_psDeproject() and p_psProject() combined
+do not seem to produce the original coordinates when they even though they
+should.  XXXDeproject() simply negates the ->r and ->d members of the output
+psSphere if the input ->y is larger than 0.0.  I don't know why it works.
+ 
+I'm guessing the p_psProject() and p_psDeproject() functions have bugs.
+ 
+XXX: It appears that p_psProject() and p_psDeproject() have been fixed.
+Remove this.
+ *****************************************************************************/
+psSphere* XXXDeproject(
+    psSphere *outSphere,
+    const psPlane* coord,
+    const psProjection* projection)
+{
+    psSphere *rc = p_psDeproject(outSphere, coord, projection);
+
+    if (coord->y >= 0.0) {
+        rc->d = -rc->d;
+        rc->r = -rc->r;
+    }
+
+    return(rc);
+}
+
+/*****************************************************************************
+  *****************************************************************************/
+psSphere* pmCoordTPToSky(
+    psSphere* outSphere,
+    const psPlane* tpCoord,
+    const psProjection *projection)
+{
+    PS_ASSERT_PTR_NON_NULL(tpCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(projection, NULL);
+
+    //    psSphere *rc = XXXDeproject(outSphere, tpCoord, projection);
+    psSphere *rc = p_psDeproject(outSphere, tpCoord, projection);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform tangent plane coords to sky coords.\n");
+    }
+    return(rc);
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+psPlane* pmCoordSkyToTP(
+    psPlane* tpCoord,
+    const psSphere* in,
+    const psProjection *projection)
+{
+    PS_ASSERT_PTR_NON_NULL(in, NULL);
+    PS_ASSERT_PTR_NON_NULL(projection, NULL);
+
+    psPlane *rc = p_psProject(tpCoord, in, projection);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform sky to tangent plane coords.\n");
+    }
+    return(rc);
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+psSphere* pmCoordCellToSky(
+    psSphere* skyCoord,
+    const psPlane* cellCoord,
+    double color,
+    double magnitude,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cellCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->toFPA, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent->parent, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent->parent->toTangentPlane, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent->parent->projection, NULL);
+    psPlane fpaCoord;
+    psPlane tpCoord;
+    psPlane *rc;
+    pmFPA* parFPA = (cell->parent)->parent;
+
+    // Convert the input cell coordinates to FPA coordinates.
+    rc = psPlaneTransformApply(&fpaCoord, cell->toFPA, cellCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could transform cell coords to FPA coords.\n");
+        return(NULL);
+    }
+
+    // Convert the FPA coordinates to tangent plane Coordinates.
+    rc = psPlaneDistortApply(&tpCoord, parFPA->toTangentPlane, &fpaCoord, color, magnitude);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could transform FPA coords to tangent plane coords.\n");
+        return(NULL);
+    }
+
+    // Convert the tangent plane Coordinates to sky coordinates.
+    psSphere *rc2 = pmCoordTPToSky(skyCoord, &tpCoord, parFPA->projection);
+    if (rc2 == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform cell coords to sky coords.\n");
+    }
+
+    return(rc2);
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+psPlane* pmCoordSkyToCell(
+    psPlane* cellCoord,
+    const psSphere* skyCoord,
+    float color,
+    float magnitude,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(skyCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->parent->parent, NULL);
+    pmChip *parChip = cell->parent;
+    pmFPA *parFPA = parChip->parent;
+    psPlane tpCoord;
+    psPlane fpaCoord;
+    psPlane chipCoord;
+    psPlane *rc;
+
+    // Convert the skyCoords to tangent plane coords.
+    rc = pmCoordSkyToTP(&tpCoord, skyCoord, parFPA->projection);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine tangent plane coords.\n");
+        return(NULL);
+    }
+
+    // Convert the tangent plane coords to FPA coords.
+    rc = pmCoordTPToFPA(&fpaCoord, &tpCoord, color, magnitude, parFPA);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine FPA coords.\n");
+        return(NULL);
+    }
+
+    // Convert the FPA coords to chip coords.
+    rc = pmCoordFPAToChip(&chipCoord, &fpaCoord, parChip);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine chip coords.\n");
+        return(NULL);
+    }
+
+    // Convert the chip coords to cell coords.
+    rc = pmCoordChipToCell(cellCoord, &chipCoord, cell);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not determine cell coords.\n");
+        return(NULL);
+    }
+
+    return (cellCoord);
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+psSphere* pmCoordCellToSkyQuick(
+    psSphere* outSphere,
+    const psPlane* cellCoord,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cellCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->toSky, NULL);
+    psPlane outPlane;
+    psPlane *rc;
+    rc = psPlaneTransformApply(&outPlane, cell->toSky, cellCoord);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could transform cell coords to sky coords.\n");
+        return(NULL);
+    }
+
+    psSphere *out = outSphere;
+    if (out == NULL) {
+        out = psSphereAlloc();
+    }
+    out->r = outPlane.y;
+    out->d = outPlane.x;
+
+    return(out);
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+psPlane* pmCoordSkyToCellQuick(
+    psPlane* cellCoord,
+    const psSphere* skyCoord,
+    const pmCell* cell)
+{
+    PS_ASSERT_PTR_NON_NULL(skyCoord, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+    PS_ASSERT_PTR_NON_NULL(cell->toSky, NULL);
+    psPlane skyPlane;
+    skyPlane.y = skyCoord->r;
+    skyPlane.x = skyCoord->d;
+
+    psPlane *rc = psPlaneTransformApply(cellCoord, cell->toSky, &skyPlane);
+    if (rc == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not transform sky to cell coords.\n");
+    }
+    return(cellCoord);
+}
Index: /tags/RHL_v1_1/psModules/src/astrom/pmFPAAstrometry.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/astrom/pmFPAAstrometry.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/astrom/pmFPAAstrometry.h	(revision 9533)
@@ -0,0 +1,188 @@
+#ifndef PM_FPA_ASTROMETRY_H
+#define PM_FPA_ASTROMETRY_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+/** Find cooresponding cell for given FPA coordinate
+ *
+ *  @return pmCell*    the cell cooresponding to the coord in FPA
+ */
+pmCell* pmCellInFPA(
+    const psPlane* coord,              ///< the coordinate in FPA plane
+    const pmFPA* FPA                   ///< the FPA to search for the cell
+);
+
+
+/** Find cooresponding chip for given FPA coordinate
+ *
+ *  @return pmChip*    the chip cooresponding to coord
+ */
+pmChip* pmChipInFPA(
+    const psPlane* coord,              ///< the coordinate in FPA plane
+    const pmFPA* FPA                   ///< the FPA to search for the cell
+);
+
+
+/** Find cooresponding cell for given Chip coordinate
+ *
+ *  @return pmCell*    the cell cooresponding to coord
+ */
+pmCell* pmCellInChip(
+    const psPlane* coord,              ///< the coordinate in Chip plane
+    const pmChip* chip                 ///< the chip to search for the cell
+);
+
+
+/** Translate a cell coordinate into a chip coordinate
+ *
+ *  @return psPlane*    the resulting chip coordinate
+ */
+psPlane* pmCoordCellToChip(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within Cell
+    const pmCell* cell                 ///< the Cell in interest
+);
+
+
+/** Translate a chip coordinate into a FPA coordinate
+ *
+ *  @return psPlane*    the resulting FPA coordinate
+ */
+psPlane* pmCoordChipToFPA(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within Chip
+    const pmChip* chip                 ///< the chip in interest
+);
+
+
+/** Translate a FPA coordinate into a Tangent Plane coordinate
+ *
+ *  @return psPlane*    the resulting Tangent Plane coordinate
+ */
+psPlane* pmCoordFPAToTP(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within FPA
+    double color,                      ///< Color of source
+    double magnitude,                  ///< Magnitude of source
+    const pmFPA* fpa                   ///< the FPA in interest
+);
+
+
+/** Translate a Tangent Plane coordinate into a Sky coordinate
+ *
+ *  @return psSphere*    the resulting Sky coordinate
+ */
+psSphere* pmCoordTPToSky(
+    psSphere* out,                     ///< a sphere struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                ///< the coordinate within Tangent Plane
+    const psProjection *projection
+);
+
+/** Translate a cell coordinate into a FPA coordinate
+ *
+ *  @return psPlane*    the resulting FPA coordinate
+ */
+psPlane* pmCoordCellToFPA(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within cell
+    const pmCell* cell                 ///< the cell in interest
+);
+
+
+/** Translate a cell coordinate into a Sky coordinate
+ *
+ *  @return psSphere*    the resulting Sky coordinate
+ */
+psSphere* pmCoordCellToSky(
+    psSphere* out,                     ///< a sphere struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within cell
+    double color,                      ///< Color of source
+    double magnitude,                  ///< Magnitude of source
+    const pmCell* cell                 ///< the cell in interest
+);
+
+
+/** Translate a cell coordinate into a Sky coordinate using a 'quick and
+ *  dirty' method
+ *
+ *  @return psSphere*    the resulting Sky coordinate
+ */
+psSphere* pmCoordCellToSkyQuick(
+    psSphere* out,                     ///< a sphere struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within cell
+    const pmCell* cell                 ///< the cell in interest
+);
+
+
+/** Translate a Sky coordinate into a Tangent Plane coordinate
+ *
+ *  @return psPlane*    the resulting Tangent Plane coordinate
+ */
+psPlane* pmCoordSkyToTP(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psSphere* in,                ///< the sky coordinate
+    const psProjection *projection
+);
+
+/** Translate a Tangent Plane coordinate into a FPA coordinate
+ *
+ *  @return psPlane*    the resulting FPA coordinate
+ */
+psPlane* pmCoordTPToFPA(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the coordinate within tangent plane
+    double color,                      ///< Color of source
+    double magnitude,                  ///< Magnitude of source
+    const pmFPA* fpa                   ///< the FPA of interest
+);
+
+
+/** Translate a FPA coordinate into a chip coordinate
+ *
+ *  @return psPlane*    the resulting chip coordinate
+ */
+psPlane* pmCoordFPAToChip(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the FPA coordinate
+    const pmChip* chip                 ///< the chip of interest
+);
+
+
+/** Translate a chip coordinate into a cell coordinate
+ *
+ *  @return psPlane*    the resulting cell coordinate
+ */
+psPlane* pmCoordChipToCell(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psPlane* in,                 ///< the Chip coordinate
+    const pmCell* cell                 ///< the cell of interest
+);
+
+
+/** Translate a sky coordinate into a cell coordinate
+ *
+ *  @return psPlane*    the resulting cell coordinate
+ */
+psPlane* pmCoordSkyToCell(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psSphere* in,                ///< the Sky coordinate
+    float color,                       ///< Color of source
+    float magnitude,                   ///< Magnitude of source
+    const pmCell* cell                 ///< the cell of interest
+);
+
+
+/** Translate a sky coordinate into a cell coordinate using a 'quick and
+ *  dirty' method
+ *
+ *  @return psPlane*    the resulting cell coordinate
+ */
+psPlane* pmCoordSkyToCellQuick(
+    psPlane* out,                      ///< a plane struct to recycle. If NULL, a new struct is created
+    const psSphere* in,                ///< the Sky coordinate
+    const pmCell* cell                 ///< the cell of interest
+);
+
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/camera/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/.cvsignore	(revision 9533)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/RHL_v1_1/psModules/src/camera/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/Makefile.am	(revision 9533)
@@ -0,0 +1,45 @@
+noinst_LTLIBRARIES = libpsmodulescamera.la
+
+libpsmodulescamera_la_CPPFLAGS = $(SRCINC) $(PSMODULES_CFLAGS)
+libpsmodulescamera_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulescamera_la_SOURCES  = \
+	pmFPA.c \
+	pmFPAConstruct.c \
+	pmFPACopy.c \
+	pmFPAHeader.c \
+	pmFPAMaskWeight.c \
+	pmFPAMosaic.c \
+	pmFPARead.c \
+	pmFPAUtils.c \
+	pmFPAWrite.c \
+	pmHDU.c \
+	pmHDUUtils.c \
+	pmHDUGenerate.c \
+	pmFPA_JPEG.c \
+	pmFPAview.c \
+	pmFPAfile.c \
+	pmFPAfileDefine.c \
+	pmFPAfileIO.c \
+	pmFPAfileFitsIO.c
+
+pkginclude_HEADERS = \
+	pmFPA.h \
+	pmFPAConstruct.h \
+	pmFPACopy.h \
+	pmFPAHeader.h \
+	pmFPAMaskWeight.h \
+	pmFPAMosaic.h \
+	pmFPARead.h \
+	pmFPAUtils.h \
+	pmFPAWrite.h \
+	pmHDU.h \
+	pmHDUUtils.h \
+	pmHDUGenerate.h \
+	pmFPA_JPEG.h \
+	pmFPAview.h \
+	pmFPAfile.h \
+	pmFPAfileDefine.h \
+	pmFPAfileIO.h \
+	pmFPAfileFitsIO.h
+
+CLEANFILES = *~
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPA.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPA.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPA.c	(revision 9533)
@@ -0,0 +1,752 @@
+/** @file  pmFPA.c
+*
+*  @brief This file defines the basic types for the FPA hierarchy
+*
+*  @ingroup AstroImage
+*
+*  @author GLG, MHPCC
+*
+* XXX: We should review the extent of the warning messages on these functions
+* when the transformations are not successful.
+*
+* XXX: Should we implement non-linear cell->chip transforms?
+*
+*  @version $Revision: 1.12 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-09-15 09:49:01 $
+*
+*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/******************************************************************************/
+/*  INCLUDE FILES                                                             */
+/******************************************************************************/
+#include <string.h>
+#include <math.h>
+#include <assert.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmConcepts.h"
+#include "pmMaskBadPixels.h"
+
+/******************************************************************************
+ *****************************************************************************/
+
+static void readoutFree(pmReadout *readout)
+{
+    // if this readout has a parent, drop that instance
+    if (readout->parent) {
+        psTrace("psModules.camera", 9, "Removing readout %zd from cell %zd...\n", (size_t)readout, (size_t)readout->parent);
+        psArray *readouts = readout->parent->readouts;
+        for (int i = 0; i < readouts->n; i++) {
+            if (readouts->data[i] == readout) {
+                readouts->data[i] = NULL;
+            }
+        }
+    }
+    psTrace("psModules.camera", 9, "Freeing readout %zd\n", (size_t) readout);
+    psFree(readout->image);
+    psFree(readout->mask);
+    psFree(readout->weight);
+    psFree(readout->analysis);
+    psFree(readout->bias);
+}
+
+static void cellFree(pmCell *cell)
+{
+
+    // if this cell has a parent, drop that instance
+    if (cell->parent) {
+        psTrace("psModules.camera", 9, "Removing cell %zd from chip %zd...\n", (size_t)cell, (size_t)cell->parent);
+        psArray *cells = cell->parent->cells;
+        for (int i = 0; i < cells->n; i++) {
+            if (cells->data[i] == cell) {
+                cells->data[i] = NULL;
+            }
+        }
+    }
+    psTrace("psModules.camera", 9, "Freeing cell %zd\n", (size_t)cell);
+    pmCellFreeReadouts(cell);
+    psFree(cell->readouts);
+
+    psFree(cell->concepts);
+    psFree(cell->analysis);
+    psFree(cell->config);
+    psFree(cell->hdu);
+}
+
+static void chipFree(pmChip* chip)
+{
+    // if this chip has a parent, drop that instance
+    if (chip->parent) {
+        psTrace("psModules.camera", 9, "Removing chip %zd from fpa %zd...\n", (size_t)chip, (size_t)chip->parent);
+        psArray *chips = chip->parent->chips;
+        for (int i = 0; i < chips->n; i++) {
+            if (chips->data[i] == chip) {
+                chips->data[i] = NULL;
+            }
+        }
+    }
+
+    psTrace("psModules.camera", 9, "Freeing chip %zd\n", (size_t)chip);
+    pmChipFreeCells(chip);
+    psFree(chip->cells);
+
+    psFree(chip->concepts);
+    psFree(chip->analysis);
+    psFree(chip->hdu);
+    psFree(chip->mosaic);
+
+    # if FPA_ASTROM
+
+    psFree(chip->toFPA);
+    psFree(chip->fromFPA);
+    # endif
+}
+
+
+static void FPAFree(pmFPA *fpa)
+{
+    psTrace("psModules.camera", 9, "Freeing fpa %zd\n", (size_t)fpa);
+
+    // NULL the parent pointers
+    psArray *chips = fpa->chips;
+    for (int i = 0 ; i < chips->n ; i++) {
+        pmChip *tmpChip = chips->data[i];
+        if (! tmpChip) {
+            continue;
+        }
+        tmpChip->parent = NULL;
+    }
+    psFree(fpa->chips);
+    psFree(fpa->concepts);
+    psFree(fpa->analysis);
+    psFree(fpa->camera);
+    psFree(fpa->hdu);
+
+    # if FPA_ASTROM
+
+    psFree(fpa->fromTangentPlane);
+    psFree(fpa->toTangentPlane);
+    psFree(fpa->projection);
+    # endif
+}
+
+void pmCellFreeReadouts(pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell,);
+
+    //
+    // Set the parent to NULL in all cell->readouts before psFree(cell->readouts)
+    // in order to avoid memory reference counter problems.
+    //
+    psArray *readouts = cell->readouts;
+    for (psS32 i = 0 ; i < readouts->n ; i++) {
+        pmReadout *tmpReadout = readouts->data[i];
+        if (! tmpReadout) {
+            continue;
+        }
+        tmpReadout->parent = NULL;
+        psTrace("psModules.camera", 9, "Will now free readout %zd...\n", (size_t)tmpReadout);
+    }
+    cell->readouts = psArrayRealloc(cell->readouts, 0);
+    cell->readouts->n = 0;
+}
+
+
+void pmChipFreeCells(pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip,);
+
+    //
+    // Set the parent to NULL in all chip->cells before psFree(chip->cells)
+    // in order to avoid memory reference counter problems.
+    //
+    psArray *cells = chip->cells;
+    for (int i = 0 ; i < cells->n ; i++) {
+        pmCell *tmpCell = cells->data[i];
+        if (! tmpCell) {
+            continue;
+        }
+        tmpCell->parent = NULL;
+        pmCellFreeReadouts(tmpCell);// Drop all readouts the cell holds
+    }
+    chip->cells = psArrayRealloc(chip->cells, 0);
+    chip->cells->n = 0;
+}
+
+void pmReadoutFreeData (pmReadout *readout)
+{
+    psFree(readout->image);
+    psFree(readout->mask);
+    psFree(readout->weight);
+    psFree(readout->bias);
+
+    readout->image = NULL;
+    readout->weight = NULL;
+    readout->mask = NULL;
+    readout->bias = psListAlloc(NULL);
+}
+
+void pmCellFreeData(pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell,);
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadoutFreeData(cell->readouts->data[i]);
+    }
+    if (cell->hdu) {
+        psFree(cell->hdu->images);
+        psFree(cell->hdu->weights);
+        psFree(cell->hdu->masks);
+        // psFree(cell->hdu->header);
+
+        cell->hdu->images = NULL;
+        cell->hdu->weights = NULL;
+        cell->hdu->masks = NULL;
+        // cell->hdu->header = NULL;
+    }
+}
+
+void pmChipFreeData(pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip,);
+
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCellFreeData(chip->cells->data[i]);
+    }
+    if (chip->hdu) {
+        psFree(chip->hdu->images);
+        psFree(chip->hdu->weights);
+        psFree(chip->hdu->masks);
+        // psFree(chip->hdu->header);
+
+        chip->hdu->images = NULL;
+        chip->hdu->weights = NULL;
+        chip->hdu->masks = NULL;
+        // chip->hdu->header = NULL;
+    }
+}
+
+void pmFPAFreeData(pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa,);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChipFreeData(fpa->chips->data[i]);
+    }
+    if (fpa->hdu) {
+        psFree(fpa->hdu->images);
+        psFree(fpa->hdu->weights);
+        psFree(fpa->hdu->masks);
+        // psFree(fpa->hdu->header);
+
+        fpa->hdu->images = NULL;
+        fpa->hdu->weights = NULL;
+        fpa->hdu->masks = NULL;
+        // fpa->hdu->header = NULL;
+    }
+}
+
+pmReadout *pmReadoutAlloc(pmCell *cell)
+{
+    pmReadout *tmpReadout = (pmReadout *)psAlloc(sizeof(pmReadout));
+    psMemSetDeallocator(tmpReadout, (psFreeFunc) readoutFree);
+
+    tmpReadout->image = NULL;
+    tmpReadout->mask = NULL;
+    tmpReadout->weight = NULL;
+    tmpReadout->bias = psListAlloc(NULL);
+    tmpReadout->analysis = psMetadataAlloc();
+    tmpReadout->parent = cell;
+    if (cell) {
+        cell->readouts = psArrayAdd(cell->readouts, 1, (psPtr) tmpReadout);
+    }
+
+    tmpReadout->process = true;            // All cells are processed by default
+    tmpReadout->file_exists = false;       // file not yet identified
+    tmpReadout->data_exists = false;       // data yet read in
+
+    tmpReadout->row0 = 0;
+    tmpReadout->col0 = 0;
+
+    return(tmpReadout);
+}
+
+pmCell *pmCellAlloc(
+    pmChip *chip,
+    const char *name
+)
+{
+    pmCell *tmpCell = (pmCell *) psAlloc(sizeof(pmCell));
+    psMemSetDeallocator(tmpCell, (psFreeFunc) cellFree);
+
+    tmpCell->config = NULL;
+    tmpCell->analysis = psMetadataAlloc();
+    tmpCell->readouts = psArrayAlloc(0);
+    tmpCell->parent = chip;
+    if (chip) {
+        chip->cells = psArrayAdd(chip->cells, 1, (psPtr) tmpCell);
+    }
+    tmpCell->hdu = NULL;
+    tmpCell->process = true;            // All cells are processed by default
+    tmpCell->file_exists = false;       // Not yet identified
+    tmpCell->data_exists = false;       // Not yet read in
+
+    tmpCell->concepts = psMetadataAlloc();
+    tmpCell->conceptsRead = PM_CONCEPT_SOURCE_NONE;
+    if (!psMetadataAddStr(tmpCell->concepts, PS_LIST_HEAD, "CELL.NAME", 0, NULL, name)) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CELL.NAME to metadata.\n");
+    }
+    pmConceptsBlankCell(tmpCell);
+
+    return tmpCell;
+}
+
+pmChip *pmChipAlloc(
+    pmFPA *fpa,
+    const char *name)
+{
+    pmChip *tmpChip = (pmChip*)psAlloc(sizeof(pmChip));
+    psMemSetDeallocator(tmpChip, (psFreeFunc) chipFree);
+
+    #if FPA_ASTROM
+
+    tmpChip->col0 = 0;
+    tmpChip->row0 = 0;
+    tmpChip->toFPA = NULL;
+    tmpChip->fromFPA = NULL;
+    # endif
+
+    tmpChip->analysis = psMetadataAlloc();
+    tmpChip->cells = psArrayAlloc(0);
+    tmpChip->parent = fpa;
+    if (fpa) {
+        psArrayAdd(fpa->chips, 1, (psPtr)tmpChip);
+    }
+    tmpChip->hdu = NULL;
+    tmpChip->mosaic = NULL;
+    tmpChip->process = true;            // Work on all chips, by default
+    tmpChip->file_exists = false;       // Not yet identified
+    tmpChip->data_exists = false;       // Not yet read in
+
+    tmpChip->concepts = psMetadataAlloc();
+    tmpChip->conceptsRead = PM_CONCEPT_SOURCE_NONE;
+    if (!psMetadataAddStr(tmpChip->concepts, PS_LIST_HEAD, "CHIP.NAME", 0, NULL, name)) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CHIP.NAME %s to concepts.\n", name);
+    }
+    pmConceptsBlankChip(tmpChip);
+    return tmpChip;
+}
+
+pmFPA *pmFPAAlloc(const psMetadata *camera)
+{
+    pmFPA *tmpFPA = (pmFPA *) psAlloc(sizeof(pmFPA));
+    psMemSetDeallocator(tmpFPA, (psFreeFunc) FPAFree);
+
+    #if FPA_ASTROM
+
+    tmpFPA->fromTangentPlane = NULL;
+    tmpFPA->toTangentPlane = NULL;
+    tmpFPA->projection = NULL;
+    # endif
+
+    tmpFPA->analysis = NULL;
+    tmpFPA->camera = psMemIncrRefCounter((psPtr)camera);
+    tmpFPA->chips = psArrayAlloc(0);
+    tmpFPA->hdu = NULL;
+
+    tmpFPA->concepts = psMetadataAlloc();
+    tmpFPA->conceptsRead = PM_CONCEPT_SOURCE_NONE;
+    pmConceptsBlankFPA(tmpFPA);
+
+    return tmpFPA;
+}
+
+static bool cellCheckParents(pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, true);
+
+    bool flag = true;
+    for (long i = 0; i < cell->readouts->n ; i++) {
+        pmReadout *tmpReadout = (pmReadout *) cell->readouts->data[i];
+        if (!tmpReadout) {
+            continue;
+        }
+        if (tmpReadout->parent != cell) {
+            tmpReadout->parent = cell;
+            flag = false;
+        }
+    }
+    return flag;
+}
+
+static bool chipCheckParents(pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, true);
+
+    bool flag = true;
+    for (long i = 0; i < chip->cells->n ; i++) {
+        pmCell *tmpCell = (pmCell *) chip->cells->data[i];
+        if (!tmpCell) {
+            continue;
+        }
+        if (tmpCell->parent != chip) {
+            tmpCell->parent = chip;
+            flag = false;
+        }
+
+        flag &= cellCheckParents(tmpCell);
+    }
+    return flag;
+}
+
+psBool pmFPACheckParents(pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, true);
+
+    bool flag = true;
+    for (long i = 0; i < fpa->chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) fpa->chips->data[i];
+        if (!tmpChip) {
+            continue;
+        }
+        if (tmpChip->parent != fpa) {
+            tmpChip->parent = fpa;
+            flag = false;
+        }
+
+        flag &= chipCheckParents(tmpChip);
+    }
+    return flag;
+}
+
+/** functions to turn on/off the file_exists flag **/
+bool pmFPASetFileStatus(pmFPA *fpa, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        pmChipSetFileStatus (chip, status);
+    }
+    return true;
+}
+
+bool pmChipSetFileStatus(pmChip *chip, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    chip->file_exists = status;
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        pmCellSetFileStatus (cell, status);
+    }
+    return true;
+}
+
+bool pmCellSetFileStatus(pmCell *cell, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+
+    cell->file_exists = status;
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        readout->file_exists = status;
+    }
+    return true;
+}
+
+bool pmFPACheckFileStatus(const pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        if (!pmChipCheckFileStatus(chip)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool pmChipCheckFileStatus(const pmChip *chip)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    if (!chip->file_exists) {
+        return false;
+    }
+
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        if (!pmCellCheckFileStatus(cell)) {
+            return false;
+        }
+    }
+    return true;
+}
+
+bool pmCellCheckFileStatus(const pmCell *cell)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    if (!cell->file_exists) {
+        return false;
+    }
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        if (!readout->file_exists) {
+            return false;
+        }
+    }
+    return true;
+}
+
+/** functions to turn on/off the data_exists flag **/
+bool pmFPASetDataStatus(pmFPA *fpa, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        pmChipSetDataStatus (chip, status);
+    }
+    return true;
+}
+
+bool pmChipSetDataStatus(pmChip *chip, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    chip->data_exists = status;
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        pmCellSetDataStatus (cell, status);
+    }
+    return true;
+}
+
+bool pmCellSetDataStatus (pmCell *cell, bool status)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+
+    cell->data_exists = status;
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        readout->data_exists = status;
+    }
+    return true;
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+
+// Set cells within a chip to be processed or not
+static bool setCellsProcess(const pmChip *chip, // Chip of interest
+                            bool process  // Process this chip?
+                           )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    psArray *cells = chip->cells;       // Component cells
+    if (! cells) {
+        return false;
+    }
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *tmpCell = cells->data[i]; // Cell of interest
+        if (tmpCell) {
+            tmpCell->process = process;
+        }
+    }
+
+    return true;
+}
+
+/*****************************************************************************
+XXX EAM : I've added the 'exclusive' option.  if true all other chips are de-selected
+XXX EAM : a negative value is valid and, in combinations with exclusive, de-selects all chips
+ *****************************************************************************/
+bool pmFPASelectChip(pmFPA *fpa, int chipNum, bool exclusive)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    psArray *chips = fpa->chips;        // Component chips
+    if ((chips == NULL) || (chipNum >= chips->n)) {
+        return(false);
+    }
+
+    for (int i = 0 ; i < chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) chips->data[i];
+        if (tmpChip == NULL) {
+            continue;
+        }
+        if (i == chipNum) {
+            tmpChip->process = true;
+            setCellsProcess(tmpChip, true);
+        } else {
+            if (exclusive) {
+                tmpChip->process = false;
+                setCellsProcess(tmpChip, false);
+            }
+        }
+
+    }
+
+    return true;
+}
+
+/*****************************************************************************
+XXX EAM : I've added the 'exclusive' option.  if true all other chips are de-selected
+XXX EAM : a negative value is valid and, in combinations with exclusive, de-selects all cells
+XXX this function should probably be re-defined to merge with 'setCellsProcess'
+ *****************************************************************************/
+bool pmChipSelectCell(pmChip *chip, int cellNum, bool exclusive)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    psArray *cells = chip->cells;       // Component cells
+    if (!cells || cellNum > cells->n) {
+        return false;
+    }
+
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];
+        if (!cell) {
+            continue;
+        }
+        if (i == cellNum) {
+            cell->process = true;
+        } else {
+            if (exclusive) {
+                cell->process = false;
+            }
+        }
+    }
+    return true;
+}
+
+/*****************************************************************************
+XXX: The SDRS is ambiguous on a few things:
+    Whether or not the other chips should be set process=true. [PAP: No]
+    Should we return the number of chip process=true before or after they're set, [PAP: After]
+ *****************************************************************************/
+/**
+ *
+ * pmFPAExcludeChip shall set process to false only for the specified chip
+ * number (chipNum). In the event that the specified chip number does not exist
+ * within the fpa, the function shall generate a warning, and perform no action.
+ * The function shall return the number of chips within the fpa that have process
+ * set to true.
+ *
+ */
+int pmFPAExcludeChip(
+    pmFPA *fpa,
+    int chipNum)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, -1);
+
+    psArray *chips = fpa->chips;        // Component chips
+    if (chips == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: fpa->chips == NULL\n");
+        return(0);
+    }
+    if ((chipNum >= chips->n) || (NULL == (pmChip *) chips->data[chipNum])) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: the specified chip (%d) does not exist.\n", chipNum);
+        return(0);
+    }
+
+    int numChips = 0;                   // Number of chips to be processed
+    for (int i = 0 ; i < chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) chips->data[i]; // Chip of interest
+        if (tmpChip != NULL) {
+            if (i == chipNum) {
+                tmpChip->process = false;
+                setCellsProcess(tmpChip, false); // Wipe out the cell as well
+            } else if (tmpChip->process) {
+                numChips++;
+            }
+        }
+    }
+
+    return(numChips);
+}
+
+int pmChipExcludeCell(pmChip *chip,
+                      int cellNum
+                     )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, -1);
+
+    psArray *cells = chip->cells;       // The component cells
+    if (!cells || cellNum > cells->n) {
+        return 0;
+    }
+
+    int numCells = 0;                   // Number of cells to be processed
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];
+        if (!cell) {
+            continue;
+        }
+        if (i == cellNum) {
+            cell->process = false;
+        } else {
+            numCells++;
+        }
+    }
+
+    return numCells;
+}
+
+static char *NameNONE = "NONE";
+static char *NameFPA = "FPA";
+static char *NameCHIP = "CHIP";
+static char *NameCELL = "CELL";
+static char *NameREADOUT = "READOUT";
+
+char *pmFPALevelToName(pmFPALevel level)
+{
+
+    switch (level) {
+    case PM_FPA_LEVEL_NONE:
+        return NameNONE;
+    case PM_FPA_LEVEL_FPA:
+        return NameFPA;
+    case PM_FPA_LEVEL_CHIP:
+        return NameCHIP;
+    case PM_FPA_LEVEL_CELL:
+        return NameCELL;
+    case PM_FPA_LEVEL_READOUT:
+        return NameREADOUT;
+    default:
+        psAbort(PS_FILE_LINE, "You can't get here; level = %d", level);
+    }
+    return NULL;
+}
+
+pmFPALevel pmFPALevelFromName(const char *name)
+{
+    pmFPALevel val;
+
+    if (name == NULL) {
+        val = PM_FPA_LEVEL_NONE;
+    } else if (!strcasecmp(name, "FPA"))     {
+        val = PM_FPA_LEVEL_FPA;
+    } else if (!strcasecmp(name, "CHIP"))    {
+        val = PM_FPA_LEVEL_CHIP;
+    } else if (!strcasecmp(name, "CELL"))    {
+        val = PM_FPA_LEVEL_CELL;
+    } else if (!strcasecmp(name, "READOUT")) {
+        val = PM_FPA_LEVEL_READOUT;
+    } else {
+        val = PM_FPA_LEVEL_NONE;
+    }
+
+    return val;
+}
+
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPA.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPA.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPA.h	(revision 9533)
@@ -0,0 +1,298 @@
+/** @file  pmFPA.h
+*
+*  @brief This file defines the basic types the focal plane hierarchy.
+*
+*  @ingroup AstroImage
+*
+*  @author GLG, MHPCC
+*
+*  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-08-02 02:17:11 $
+*
+*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+*/
+
+#ifndef PM_FPA_H
+#define PM_FPA_H
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "pslib.h"
+#include "pmHDU.h"
+
+# define FPA_ASTROM 1
+
+/// @addtogroup AstroImage
+/// @{
+
+/** Focal plane data structure
+ *
+ *  A focal plane consists of one or more chips (according to the number of
+ *  pieces of contiguous silicon). It contains metadata containers for the
+ *  concepts and analysis, a link to the parent, and pointers to the FITS header,
+ *  if that corresponds to this level (the FPA may be the PHU, but will not ever
+ *  contain pixels). For astrometry, it contains a transformation from the focal
+ *  plane to the tangent plane and the fixed pattern residuals. It is expected
+ *  that the transformation will consist of two 4D polynomials (i.e. a function
+ *  of two coordinates in position, the magnitude of the object, and the color of
+ *  the object) in order to correct for optical distortions and the effects of
+ *  the atmosphere; hence we think that it is prudent to include a reverse
+ *  transformation which will be derived from numerically inverting the forward
+ *  transformation.
+ *
+ */
+typedef struct
+{
+    # if FPA_ASTROM
+    // Astrometric transformations
+    psPlaneDistort* fromTangentPlane;   ///< Transformation from tangent plane to focal plane
+psPlaneDistort* toTangentPlane;     ///< Transformation from focal plane to tangent plane
+psProjection *projection;           ///< Projection from tangent plane to sky
+# endif
+// Information
+psMetadata *concepts;               ///< Cache for PS concepts
+unsigned int conceptsRead;          ///< Which concepts have been read
+psMetadata *analysis;               ///< FPA-level analysis metadata
+const psMetadata *camera;           ///< Camera configuration
+psArray *chips;                     ///< The chips
+pmHDU *hdu;                         ///< FITS data
+}
+pmFPA;
+
+/** Chip data structure
+ *
+ *  A chip consists of one or more cells (according to the number of amplifiers
+ *  on the device). The chip contains metadata containers for the concepts and
+ *  analysis, a link to the parent, and pointers to the pointers to the various
+ *  FITS data, if that corresponds to this level. For astrometry, in addition to
+ *  the rough positioning information, it contains a coordinate transform from
+ *  the chip to the focal plane. It is expected that this transform will consist
+ *  of two second-order 2D polynomials; hence we think that it is prudent to
+ *  include a reverse transformation which will be derived from numerically
+ *  inverting the forward transformation. A boolean indicates whether the chip is
+ *  of interest, allowing it to be excluded from analysis.
+ *
+ */
+typedef struct
+{
+# if FPA_ASTROM
+    // Offset specifying position on focal plane
+    int col0;                           ///< Offset from the left of FPA.
+int row0;                           ///< Offset from the bottom of FPA.
+// Astrometric transformations
+psPlaneTransform* toFPA;            ///< Transformation from chip to FPA coordinates
+psPlaneTransform* fromFPA;          ///< Transformation from FPA to chip coordinates
+# endif
+// Information
+psMetadata *concepts;               ///< Cache for PS concepts
+unsigned int conceptsRead;          ///< Which concepts have been read
+psMetadata *analysis;               ///< Chip-level analysis metadata
+psArray *cells;                     ///< The cells (referred to by name)
+pmFPA *parent;                      ///< Parent FPA
+bool process;                       ///< Do we bother about reading and working with this chip?
+bool file_exists;                   ///< Does the file for this chip exist (read case only)?
+bool data_exists;                   ///< Does the data for this chip exist (read case only)?
+pmHDU *hdu;                         ///< FITS data
+struct pmCell *mosaic;              ///< A mosaic cell
+}
+pmChip;
+
+/** Cell data structure
+ *
+ *  A cell consists of one or more readouts.  It also contains a pointer to the
+ *  cell's metadata, and its parent chip.  On the astrometry side, it also
+ *  contains coordinate transforms from the cell to chip, from the cell to
+ *  focal-plane, as well as a "quick and dirty" tranform from the cell to
+ *  sky coordinates.
+ *
+ */
+typedef struct
+{
+psMetadata *concepts;               ///< Cache for PS concepts
+unsigned int conceptsRead;          ///< Which concepts have been read
+psMetadata *config;                 ///< Cell configuration info
+psMetadata *analysis;               ///< Cell-level analysis metadata
+psArray *readouts;                  ///< The readouts (referred to by number)
+pmChip *parent;                     ///< Parent chip
+bool process;                       ///< Do we bother about reading and working with this cell?
+bool file_exists;                   ///< Does the file for this cell exist (read case only)?
+bool data_exists;                   ///< Does the data for this cell exist (read case only)?
+pmHDU *hdu;                         ///< FITS data
+}
+pmCell;
+
+/** Readout data structure.
+ *
+ *  A readout is the result of a single read of a cell (or a portion thereof).
+ *  It contains the offset from the lower-left corner of the chip, in the case
+ *  that the CCD was windowed, as well as the binning factors and parity (if the
+ *  binning value is negative, then the parity is reversed). It also contains the
+ *  pixel data, metadata containers for the concepts and analysis, and a link to
+ *  the parent.
+ *
+ */
+typedef struct
+{
+int col0;                           ///< Column offset; non-zero if reading in columns bit by bit
+int row0;                           ///< Row offset; non-zero if reading in rows bit by bit
+psImage *image;                     ///< Imaging area of readout
+psImage *mask;                      ///< Mask of input image
+psImage *weight;                    ///< Weight of input image
+psList *bias;                       ///< Overscan images
+psMetadata *analysis;               ///< Readout-level analysis metadata
+pmCell *parent;                     ///< Parent cell
+bool process;                       ///< Do we bother about reading and working with this readout?
+bool file_exists;                   ///< Does the file for this readout exist (read case only)?
+bool data_exists;                   ///< Does the data for this readout exist (read case only)?
+}
+pmReadout;
+
+void pmCellFreeReadouts(pmCell *cell);
+void pmChipFreeCells(pmChip *chip);
+
+void pmReadoutFreeData (pmReadout *readout);
+void pmCellFreeData(pmCell *cell);
+void pmChipFreeData(pmChip *chip);
+void pmFPAFreeData(pmFPA *fpa);
+
+/** Allocates a pmReadout
+ *
+ *  The constructor shall make an empty pmReadout. If the parent cell is not
+ *  NULL, the parent link is made and the readout shall be placed in the
+ *  parents array of readouts. The metadata containers shall be allocated. All
+ *  other pointers in the structure shall be initialized to NULL.
+ *
+ *  @return pmReadout*    newly allocated pmReadout with all internal pointers set to NULL
+ */
+pmReadout *pmReadoutAlloc(
+    pmCell *cell                        ///< Parent cell
+);
+
+/** Allocates a pmCell
+ *
+ *  The constructor shall make an empty pmCell. If the parent chip is not NULL,
+ *  the parent link is made and the cell shall be placed in the parents array of
+ *  cells. The readouts array shall be allocated with a zero size, and the
+ *  metadata containers constructed. All other pointers in the structure shall be
+ *  initialized to NULL.
+ *
+ *  @return pmCell*    newly allocated pmCell
+ */
+pmCell *pmCellAlloc(
+    pmChip *chip,       ///< Parent chip
+    const char *name    ///< Name of cell
+);
+
+/** Allocates a pmChip
+ *
+ *  The constructor shall make an empty pmChip. If the parent fpa is not NULL,
+ *  the parent link is made and the chip shall be placed in the parent's array
+ *  of chips. The cells array shall be allocated with a zero size, and the
+ *  metadata containers constructed. All other pointers in the structure shall be
+ *  initialized to NULL.
+ *
+ *  @return pmChip*    newly allocated pmChip
+ */
+pmChip *pmChipAlloc(
+    pmFPA *fpa,                         ///< FPA to which the chip belongs
+    const char *name                    ///< Name of chip
+);
+
+/** Allocates a pmFPA
+ *
+ *  The constructor shall make an empty pmFPA. The chips array shall be
+ *  allocated with a zero size, the camera and db pointers set to the values
+ *  provided, and the concepts metadata constructed. All other pointers in the
+ *  structure shall be initialized to NULL.
+ *
+ */
+pmFPA *pmFPAAlloc(
+    const psMetadata *camera            ///< Camera configuration
+);
+
+
+/** Verify parent links.
+ *
+ *  This function checks the validity of the parent links in the FPA hierarchy.
+ *  If a parent link is not set (or not set correctly), it is corrected, and the
+ *  function shall return false. If all the parent pointers were correct, the
+ *  function shall return true.
+ *
+ */
+bool pmFPACheckParents(
+    pmFPA *fpa
+);
+
+/* functions to turn on/off the file_exists flags */
+bool pmFPASetFileStatus (pmFPA *fpa, bool status);
+bool pmChipSetFileStatus (pmChip *chip, bool status);
+bool pmCellSetFileStatus (pmCell *cell, bool status);
+
+/* Functions to check the file_exists flags */
+bool pmFPACheckFileStatus(const pmFPA *fpa);
+bool pmChipCheckFileStatus(const pmChip *chip);
+bool pmCellCheckFileStatus(const pmCell *cell);
+
+/* functions to turn on/off the data_exists flags */
+bool pmFPASetDataStatus (pmFPA *fpa, bool status);
+bool pmChipSetDataStatus (pmChip *chip, bool status);
+bool pmCellSetDataStatus (pmCell *cell, bool status);
+
+/** Specify the level for an operation.
+ */
+typedef enum {
+    PM_FPA_LEVEL_NONE,                  ///< No particular level specified
+    PM_FPA_LEVEL_FPA,                   ///< Level corresponds to an FPA
+    PM_FPA_LEVEL_CHIP,                  ///< Level corresponds to a Chip
+    PM_FPA_LEVEL_CELL,                  ///< Level corresponds to a Cell
+    PM_FPA_LEVEL_READOUT                ///< Level corresponds to a Readout
+} pmFPALevel;
+
+
+/**
+ *
+ * pmFPASelectChip shall set valid to true for the specified chip number
+ * (chipNum), and all other chips shall have valid set to false. In the event
+ * that the specified chip number does not exist within the fpa, the function
+ * shall return false.
+ *
+ */
+bool pmFPASelectChip(
+    pmFPA *fpa,
+    int chipNum,
+    bool exclusive
+);
+
+bool pmChipSelectCell(pmChip *chip,
+                      int cellNum,
+                      bool exclusive
+                     );
+
+/**
+ *
+ * pmFPAExcludeChip shall set valid to false only for the specified chip
+ * number (chipNum). In the event that the specified chip number does not exist
+ * within the fpa, the function shall generate a warning, and perform no action.
+ * The function shall return the number of chips within the fpa that have valid
+ * set to true.
+ *
+ */
+int pmFPAExcludeChip(
+    pmFPA *fpa,
+    int chipNum
+);
+
+int pmChipExcludeCell(pmChip *chip,
+                      int cellNum
+                     );
+
+// Set the weights and masks within a cell, based on the gain and RN
+bool pmCellSetWeights(pmCell *cell // Cell for which to set weights
+                     );
+
+
+char *pmFPALevelToName(pmFPALevel level);
+pmFPALevel pmFPALevelFromName(const char *name);
+
+#endif // #ifndef PM_FPA_H
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAConstruct.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAConstruct.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAConstruct.c	(revision 9533)
@@ -0,0 +1,872 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmFPA.h"
+#include "pmConcepts.h"
+#include "pmFPAConstruct.h"
+#include "pmFPAview.h"
+#include "pmFPAUtils.h"
+#include "pmHDUUtils.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Read data for a particular cell from the camera format description
+static psMetadata *getCellData(const psMetadata *format, // The camera format description
+                               const char *cellName // The name of the cell
+                              )
+{
+    assert(format);
+    assert(cellName && strlen(cellName) > 0);
+
+    bool status = true;                 // Result of MD lookup
+    psMetadata *cells = psMetadataLookupMD(&status, format, "CELLS"); // The CELLS
+    if (!status || !cells) {
+        psError(PS_ERR_IO, true, "Unable to determine CELLS of camera.\n");
+        return NULL;
+    }
+
+    psMetadata *cellData = psMetadataLookupMD(&status, cells, cellName); // The data for the particular cell
+    if (!status || !cellData) {
+        psLogMsg(__func__, PS_LOG_WARN, "Unable to find specs for cell %s: ignored\n", cellName);
+    }
+
+    return cellData;
+}
+
+// Parse a list of first:second:third pairs in a string
+static bool parseContent(psArray **first, // Array of the first values
+                         psArray **second, // Array of the second values
+                         psArray **third, // Array of the third values
+                         const char *string // The string to parse
+                        )
+{
+    assert(first);
+    assert(second);
+    assert(third);
+    assert(string && strlen(string) > 0);
+
+    bool allOK = true;                  // Everything was OK?
+    psList *values = psStringSplit(string, " ,;", true); // List of the parts
+    *first = psArrayAlloc(values->n);
+    *second = psArrayAlloc(values->n);
+    *third = psArrayAlloc(values->n);
+    (*first)->n = (*second)->n = (*third)->n = values->n;
+    int num = 0;
+    psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false); // Iterator for values
+    psString value = NULL;               // "first:second:third" string
+    while ((value = psListGetAndIncrement(valuesIter))) {
+        psList *firstSecondThird = psStringSplit(value, ":", true); // List containing the first, second, third
+        psArray *fst = psListToArray(firstSecondThird); // An array representation
+        psFree(firstSecondThird);
+        psString firstPart = NULL;      // The first part
+        psString secondPart = NULL;     // The second part
+        psString thirdPart = NULL;      // The third part
+        switch (firstSecondThird->n) {
+        case 1:
+            thirdPart = fst->data[0];
+            break;
+        case 2:
+            secondPart = fst->data[0];
+            thirdPart = fst->data[1];
+            break;
+        case 3:
+            firstPart = fst->data[0];
+            secondPart = fst->data[1];
+            thirdPart = fst->data[2];
+            break;
+        default:
+            psLogMsg(__func__, PS_LOG_WARN, "Badly formated specifier: %s --- ignored.\n", value);
+            allOK = false;
+            psFree(fst);
+            continue;
+        }
+        psArraySet(*first, num, firstPart);
+        psArraySet(*second, num, secondPart);
+        psArraySet(*third, num, thirdPart);
+        num++;
+        psFree(fst);
+    }
+    psFree(valuesIter);
+    psFree(values);
+
+    return allOK;
+}
+
+// Get the name of a PHU chip or cell from the header
+static psString phuNameFromHeader(const char *name, // The name to lookup: "CELL.NAME" or "CHIP.NAME"
+                                  const psMetadata *fileInfo, // FILE within the camera format description
+                                  const psMetadata *header // Primary header
+                                 )
+{
+    assert(name && strlen(name) > 0);
+    assert(fileInfo);
+    assert(header);
+
+    bool mdok = true;                   // Result of MD lookup
+    psString keyword = psMetadataLookupStr(&mdok, fileInfo, name);
+    if (!mdok || strlen(keyword) == 0) {
+        return false;
+    }
+    psMetadataItem *resultItem = psMetadataLookup(header, keyword);
+    if (!resultItem) {
+        psError(PS_ERR_IO, true, "Unable to find %s in primary header to identify %s.\n", keyword, name);
+        return NULL;
+    }
+    return psMetadataItemParseString(resultItem);
+}
+
+// Add an HDU to the FPA
+static bool addHDUtoFPA(pmFPA *fpa,     // FPA to which to add
+                        pmHDU *hdu      // HDU to be added
+                       )
+{
+    assert(fpa);
+    assert(hdu);
+
+    if (fpa->hdu) {
+        // Something's already here
+        if (fpa->hdu != hdu) {
+            psError(PS_ERR_IO, true, "Unable to add HDU since FPA already has one.\n");
+        }
+        return false;
+    }
+    fpa->hdu = psMemIncrRefCounter(hdu);
+    bool success = true;                // Success of reading concepts
+    if (hdu->header && !pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER, false, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts from header.\n");
+        success = false;
+    }
+    pmFPASetFileStatus(fpa, true);
+
+    return success;
+}
+
+// Add an HDU to the chip
+static bool addHDUtoChip(pmChip *chip,  // Chip to which to add
+                         pmHDU *hdu     // HDU to be added
+                        )
+{
+    assert(chip);
+    assert(hdu);
+
+    if (chip->hdu) {
+        // Something's already here
+        if (chip->hdu != hdu) {
+            psError(PS_ERR_IO, true, "Unable to add HDU since chip already has one.\n");
+        }
+        return false;
+    }
+    chip->hdu = psMemIncrRefCounter(hdu);
+    bool success = true;                // Success of reading concepts
+    if (hdu->header && !pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER, true, false, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts from header.\n");
+        success = false;
+    }
+    pmChipSetFileStatus(chip, true);
+
+    return success;
+}
+
+// Add an HDU to the cell
+static bool addHDUtoCell(pmCell *cell,  // Cell to which to add
+                         pmHDU *hdu     // HDU to be added
+                        )
+{
+    assert(cell);
+    assert(hdu);
+
+    if (cell->hdu) {
+        // Something's already here
+        if (cell->hdu != hdu) {
+            psError(PS_ERR_IO, true, "Unable to add HDU since cell already has one.\n");
+        }
+        return false;
+    }
+    cell->hdu = psMemIncrRefCounter(hdu);
+    bool success = true;                // Success of reading concepts
+    if (hdu->header && !pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts from header.\n");
+        success = false;
+    }
+    pmCellSetFileStatus(cell, true);
+
+    return success;
+}
+
+
+// Looks up the particular content, based on the chip and cell
+static const char *getContent(const psMetadata *fileInfo, // The FILE from the camera format configuration
+                              const psMetadata *contents, // The CONTENTS from the camera format configuration
+                              const pmChip *chip, // The chip of interest
+                              const pmCell *cell // The cell of interest
+                             )
+{
+    assert(fileInfo);
+    assert(contents);
+    assert(chip);
+
+    bool mdok = true;                   // Status of MD lookup
+    char *contentKey = psMetadataLookupStr(&mdok, fileInfo, "CONTENT"); // Key for CONTENTS
+    if (!mdok || !contentKey || strlen(contentKey) == 0) {
+        psError(PS_ERR_IO, true, "Unable to find CONTENT in FILE within camera format configuration.\n");
+        return NULL;
+    }
+
+    contentKey = psStringCopy(contentKey); // So that we're not altering something someone else might use...
+
+    // Replace some concept names
+    if (strstr(contentKey, "{CHIP.NAME}")) {
+        if (!chip) {
+            psError(PS_ERR_IO, true, "CONTENT in FILE refers to CHIP.NAME, but no chip was provided.\n");
+            psFree(contentKey);
+            return NULL;
+        }
+        const char *name = psMetadataLookupStr(&mdok, chip->concepts, "CHIP.NAME");
+        if (mdok && name && strlen(name) > 0) {
+            contentKey = psStringSubstitute(contentKey, name, "{CHIP.NAME}");
+        }
+    }
+
+    if (strstr(contentKey, "{CELL.NAME}")) {
+        if (!chip) {
+            psError(PS_ERR_IO, true, "CONTENT in FILE refers to CELL.NAME, but no chip was provided.\n");
+            psFree(contentKey);
+            return NULL;
+        }
+        const char *name = psMetadataLookupStr(&mdok, cell->concepts, "CELL.NAME");
+        if (mdok && name && strlen(name) > 0) {
+            contentKey = psStringSubstitute(contentKey, name, "{CELL.NAME}");
+        }
+    }
+
+    // MORE SUBSTITUTION OPTIONS HERE!
+
+    psTrace("psModules.camera", 5, "Looking up %s in the CONTENTS.\n", contentKey);
+    const char *content = psMetadataLookupStr(&mdok, contents, contentKey);
+    if (!mdok || !content || strlen(content) == 0) {
+        psFree(contentKey);
+        psError(PS_ERR_IO, true, "Unable to find %s in the CONTENTS.\n", contentKey);
+        return NULL;
+    }
+
+    psFree(contentKey);
+
+    return content;
+}
+
+
+// Given a (string) list of contents "chip:cell:type chip:cell:type", put the HDU in the correct place and
+// plug in the cell configuration information
+static int processContents(pmFPA *fpa,  // The FPA
+                           pmChip *chip, // The chip, or NULL
+                           pmCell *cell, // The cell, or NULL
+                           pmHDU *hdu,  // The HDU to be added
+                           pmFPALevel level, // The level at which to add the HDU
+                           const char *contents, // The contents line, consisting of a list of chip:cell
+                           const psMetadata *format // Camera format configuration
+                          )
+{
+    assert(fpa);
+    assert(contents && strlen(contents) > 0);
+    assert(!cell || (cell && chip));    // Need both chip and cell if given a cell
+    assert(format);
+
+    if (hdu && level == PM_FPA_LEVEL_FPA) {
+        if (!addHDUtoFPA(fpa, hdu)) {
+            psError(PS_ERR_UNKNOWN, false, "Adding HDU to FPA (%s)", contents);
+            return -1;
+        }
+    }
+
+    // Parse the list of chip:cell:type
+    psArray *chips = NULL;              // The chips (first bits)
+    psArray *cells = NULL;              // The cells (second bits)
+    psArray *types = NULL;              // The cell types (third bits)
+    int numCells = 0;                   // Number of cells processed
+    parseContent(&chips, &cells, &types, contents);
+    for (int i = 0; i < types->n; i++) {
+        psString chipName = chips->data[i]; // The name of the chip
+        psString cellName = cells->data[i]; // The name of the cell
+        psString cellType = types->data[i]; // The type of the cell
+
+        // Get the chip
+        pmChip *newChip = NULL;         // The chip specified
+        if (chip) {
+            newChip = chip;
+        } else if (chipName) {
+            // Find the chip
+            int chipNum = pmFPAFindChip(fpa, chipName); // The chip we're looking for
+            if (chipNum == -1) {
+                psLogMsg(__func__, PS_LOG_WARN, "Unable to find chip %s in fpa --- ignored.\n", chipName);
+                continue;
+            }
+            newChip = fpa->chips->data[chipNum];
+        }
+
+        if (!newChip) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to determine chip for entry %d of content (follows) --- "
+                     "ignored.\n\t%s", i, contents);
+            continue;
+        }
+
+        // Put in the HDU
+        if (hdu && level == PM_FPA_LEVEL_CHIP) {
+            addHDUtoChip(newChip, hdu);
+        }
+        // load chip-related concepts
+        if (!pmConceptsReadChip(newChip, PM_CONCEPT_SOURCE_DEFAULTS, false, false, NULL)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read concepts from camera and defaults for chip %s\n",
+                    chipName);
+            return -1;
+        }
+
+        // Get the cell
+        pmCell *newCell = NULL;         // The cell specified
+        if (cell) {
+            newCell = cell;
+        } else if (cellName) {
+            // Find the cell
+            int cellNum = pmChipFindCell(newChip, cellName); // The cell we're looking for
+            if (cellNum == -1) {
+                psLogMsg(__func__, PS_LOG_WARN, "Unable to find cell %s in chip %s --- ignored.\n",
+                         cellName, chipName);
+                continue;
+            }
+            newCell = newChip->cells->data[cellNum];
+        }
+        if (!newCell) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to determine cell for entry %d of content (follows) --- "
+                     "ignored.\n\t%s", i, contents);
+            continue;
+        }
+
+        // Get the type
+        if (!cellType) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to determine cell type for entry %d of content (follows) "
+                     "--- ignored.\n\t%s", i, contents);
+            continue;
+        }
+        psMetadata *cellData = getCellData(format, cellType); // Data for this cell
+
+        if (hdu && level == PM_FPA_LEVEL_CELL) {
+            addHDUtoCell(newCell, hdu);
+        }
+
+        // Put in the cell data
+        if (newCell->config) {
+            psLogMsg(__func__, PS_LOG_WARN, "Overwriting cell data in chip %s, cell %s\n", chipName,
+                     cellName);
+            psFree(newCell->config); // Make way!
+        }
+        newCell->config = psMemIncrRefCounter(cellData);
+        if (!pmConceptsReadCell(newCell, PM_CONCEPT_SOURCE_CAMERA | PM_CONCEPT_SOURCE_DEFAULTS,
+                                false, NULL)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read concepts from camera and defaults for chip %s, "
+                    "cell %s, type %s", chipName, cellName, cellType);
+            return -1;
+        }
+        numCells++;
+    }
+    psFree(chips);
+    psFree(cells);
+    psFree(types);
+
+    return numCells;
+}
+
+static pmFPALevel hduLevel(const psMetadata *format // The camera format configuration
+                          )
+{
+    assert(format);
+
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *file = psMetadataLookupMD(&mdok, format, "FILE"); // File information
+    if (!mdok || !file) {
+        psError(PS_ERR_IO, true, "Unable to find FILE information in camera format configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+    const char *extType = psMetadataLookupStr(&mdok, file, "EXTENSIONS");
+    if (!mdok || !extType || strlen(extType) == 0) {
+        psError(PS_ERR_IO, true, "Unable to find EXTENSIONS in the FILE information in the camera format"
+                " configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+
+    // Where do we stick in the HDUs?
+    pmFPALevel level = PM_FPA_LEVEL_NONE; // Level for HDU insertion
+    if (strcasecmp(extType, "CHIP") == 0) {
+        level = PM_FPA_LEVEL_CHIP;
+    } else if (strcasecmp(extType, "CELL") == 0) {
+        level = PM_FPA_LEVEL_CELL;
+    } else if (strcasecmp(extType, "NONE") != 0) {
+        psError(PS_ERR_IO, true, "EXTENSIONS is not CHIP or CELL or NONE.\n");
+    }
+
+    return level;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Construct an FPA instance on the basis of a camera configuration
+pmFPA *pmFPAConstruct(const psMetadata *camera // The camera configuration
+                     )
+{
+    PS_ASSERT_PTR_NON_NULL(camera, NULL);
+
+    pmFPA *fpa = pmFPAAlloc(camera);    // The FPA to fill out
+
+    bool mdok = true;                   // Status from MD lookups
+    psMetadata *components = psMetadataLookupMD(&mdok, camera, "FPA"); // FPA components
+    if (!mdok || !components) {
+        psError(PS_ERR_IO, true, "Failed to lookup \"FPA\"");
+        psFree(fpa);
+        return NULL;
+    }
+    psMetadataIterator *componentsIter = psMetadataIteratorAlloc(components, PS_LIST_HEAD, NULL);
+    psMetadataItem *componentsItem = NULL; // Item from components
+    while ((componentsItem = psMetadataGetAndIncrement(componentsIter))) {
+        const char *chipName = componentsItem->name; // Name of the chip
+        if (componentsItem->type != PS_DATA_STRING) {
+            psLogMsg(__func__, PS_LOG_WARN, "Element %s in FPA within the camera configuration is not of "
+                     "type STR (type=%x) --- ignored.\n", chipName, componentsItem->type);
+            continue;
+        }
+
+        pmChip *chip = pmChipAlloc(fpa, chipName); // The chip
+        psList *cellNames = psStringSplit(componentsItem->data.V, " ,;", true); // List of cell names
+        psListIterator *cellNamesIter = psListIteratorAlloc(cellNames, PS_LIST_HEAD, false); // Iterator
+
+        psString cellName = NULL;       // Name of cell
+        while ((cellName = psListGetAndIncrement(cellNamesIter))) {
+            pmCell *cell = pmCellAlloc(chip, cellName); // New cell
+            psFree(cell);               // Drop reference
+        }
+        psFree(chip);                   // Drop reference
+        psFree(cellNamesIter);
+        psFree(cellNames);
+    }
+    psFree(componentsIter);
+
+    return fpa;
+}
+
+
+// This is the engine for the pmFPAAddSourceFrom{Header,View} functions
+// It returns a view corresponding to the PHU
+static pmFPAview *addSource(pmFPA *fpa,       // The FPA
+                            const pmFPAview *phuView, // The view corresponding to the PHU, or NULL
+                            const psMetadata *header, // The PHU header, or NULL
+                            const psMetadata *format // Format of file
+                           )
+{
+    assert(fpa);
+    assert(phuView || header);
+    assert(format);
+
+    bool mdok = true;                   // Status from metadata lookups
+    psMetadata *fileInfo = psMetadataLookupMD(&mdok, format, "FILE"); // The file information
+    if (!mdok || !fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
+        return NULL;
+    }
+
+    // At what level does the PHU go?
+    const char *phuType = psMetadataLookupStr(&mdok, fileInfo, "PHU"); // What is the PHU?
+    if (!mdok || strlen(phuType) == 0) {
+        psError(PS_ERR_IO, false, "Unable to find PHU in the format specification.\n");
+        return NULL;
+    }
+
+    // Prepare the PHU to be placed in the camera hierarchy
+    pmHDU *phdu = pmHDUAlloc(NULL);     // The primary header data unit
+    // Casting to psPtr to avoide "warning: passing arg 1 of `p_psMemIncrRefCounter' discards qualifiers from
+    // pointer target type"
+    phdu->header = psMemIncrRefCounter((const psPtr)header);
+    phdu->format = psMemIncrRefCounter((const psPtr)format);
+    pmFPAview *view = pmFPAviewAlloc(0); // View, to be returned
+    if (phuView) {
+        // Copy the view, for the case where we're given a header.
+        *view = *phuView;
+    }
+
+    // And at what level do the individual extensions go?
+    const char *extType = psMetadataLookupStr(&mdok, fileInfo, "EXTENSIONS"); // What's in the extns?
+    if (!mdok || strlen(extType) == 0) {
+        psError(PS_ERR_IO, false, "Unable to find EXTENSIONS in the format specification.\n");
+        psFree(view);
+        return NULL;
+    }
+
+    // Now, there are a few cases:
+
+    // 1. PHU=FPA and EXTENSIONS=NONE.  This is a single CCD imager, where the entire FPA is in the PHU image.
+    // In this case, the CONTENTS is of type STR (rather than METADATA), and has the usual chip:cell:cellType.
+    //
+    // 2. PHU=CHIP or PHU=CELL, and EXTENSIONS=NONE.  This is a single chip or cell from a mosaic camera in
+    // its own file (e.g., Megacam split).  In this case, the CONTENTS is of type METADATA, and consists of a
+    // menu of choices for the contents of the file.  This is because we need to work out which chip and/or
+    // cell it comes from.
+    //
+    // 3. EXTENSIONS=CHIP or EXTENSIONS=CELL.  This is a mosaic camera, with multiple extensions.  In this
+    // case, the CONTENTS is of type METADATA, and consists of a list of contents of the file.
+    //
+    // In all of the above cases, the contents are specified by chip:cell:cellType triples.  It's just how the
+    // contents as a *whole* are collected that changes, and what that collection means.
+    //
+    // We deal with each of these cases in turn.
+
+    // Case 1: PHU=FPA and EXTENSIONS=NONE.  We need to parse the single list of chip:cell:cellType entries.
+    if (strcasecmp(phuType, "FPA") == 0 && strcasecmp(extType, "NONE") == 0) {
+        phdu->blankPHU = false;
+        const char *contents = psMetadataLookupStr(&mdok, format, "CONTENTS"); // The contents of the file
+        if (!mdok || !contents || strlen(contents) == 0) {
+            psError(PS_ERR_IO, true, "Unable to find CONTENTS in the camera format configuration.\n");
+            psFree(phdu);
+            psFree(view);
+            return NULL;
+        }
+        if (processContents(fpa, NULL, NULL, phdu, PM_FPA_LEVEL_FPA, contents, format) < 0) {
+            psError(PS_ERR_IO, false, "Error setting CONTENTS");
+            psFree(phdu);
+            psFree(view);
+            return NULL;
+        }
+        psFree(phdu);
+
+        if (header && !pmConceptsRead(fpa, NULL, NULL, PM_CONCEPT_SOURCE_PHU, NULL)) {
+            psWarning("Unable to read concepts from PHU.\n");
+        }
+
+        return view;
+    }
+
+    // In cases 2 and 3, the CONTENTS is of type METADATA, and is either a menu (if EXTENSIONS=NONE), or a
+    // list of extensions otherwise.
+    psMetadata *contents = psMetadataLookupMD(&mdok, format, "CONTENTS"); // The contents of the FITS file
+    if (!mdok || !contents) {
+        if (mdok && !contents) {
+            psError(PS_ERR_IO, true, "CONTENTS metadata is NULL in the camera format configuration.");
+        } else {
+            if(psMetadataLookup(format, "CONTENTS") != NULL) {
+                psError(PS_ERR_IO, true, "CONTENTS is of wrong type in camera format configuration");
+            } else {
+                psError(PS_ERR_IO, true, "Unable to find CONTENTS in the camera format configuration.");
+            }
+        }
+        psFree(phdu);
+        psFree(view);
+        return NULL;
+    }
+
+    // Case 2: EXTENSIONS=NONE.  We only have the PHU.  The value of CONTENT tells us a header keyword, which
+    // gives us the key to the CONTENTS menu.  Or, if we've got the view specifying the PHU, we can get the
+    // chip/cell directly from that.
+    if (strcasecmp(extType, "NONE") == 0) {
+        phdu->blankPHU = false;
+        pmChip *chip = NULL;        // The chip of interest
+        pmCell *cell = NULL;        // The cell of interest
+        pmFPALevel level = PM_FPA_LEVEL_NONE; // Level for HDU to be added
+
+        if (phuView) {
+            // We can get the chip/cell from the view
+            if (phuView->chip != -1 && phuView->chip < fpa->chips->n) {
+                chip = fpa->chips->data[phuView->chip];
+                level = PM_FPA_LEVEL_CHIP;
+                if (phuView->cell != -1) {
+                    if (phuView->cell < chip->cells->n) {
+                        cell = chip->cells->data[phuView->cell];
+                        level = PM_FPA_LEVEL_CELL;
+                    } else {
+                        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "PHU view for cell (%d) does not "
+                                "correspond to camera format.\n", phuView->cell);
+                        psFree(phdu);
+                        return NULL;
+                    }
+                }
+            } else {
+                psError(PS_ERR_BAD_PARAMETER_VALUE, true, "PHU view for chip (%d) does not correspond to "
+                        "camera format.\n", phuView->chip);
+                psFree(phdu);
+                return NULL;
+            }
+        } else {
+            // Need to look up what chip we have.
+            psString chipName = phuNameFromHeader("CHIP.NAME", fileInfo, header);
+            psTrace("psModules.camera", 5, "This is chip %s\n", chipName);
+            int chipNum = pmFPAFindChip(fpa, chipName); // Chip number
+            if (chipNum == -1) {
+                psError(PS_ERR_IO, true, "Unable to find chip %s in FPA.\n", chipName);
+                psFree(phdu);
+                psFree(view);
+                return NULL;
+            }
+            chip = fpa->chips->data[chipNum]; // Chip of interest
+            view->chip = chipNum;
+
+            if (strcasecmp(phuType, "CHIP") == 0) {
+                level = PM_FPA_LEVEL_CHIP;
+            } else if (strcasecmp(phuType, "CELL") == 0) {
+                level = PM_FPA_LEVEL_CELL;
+                // Need to look up what cell we have.
+                psString cellName = phuNameFromHeader("CELL.NAME", fileInfo, header);
+                int cellNum = pmChipFindCell(chip, cellName); // Cell number
+                if (cellNum == -1) {
+                    psError(PS_ERR_IO, true, "Unable to find cell %s in chip %s.\n", cellName, chipName);
+                    psFree(view);
+                    psFree(phdu);
+                    return NULL;
+                }
+                cell = chip->cells->data[cellNum];
+                view->cell = cellNum;
+                psFree(cellName);
+            } else {
+                // We have already dealt with the case PHU=FPA, in a special case above, so if we get here
+                // it's an error.
+                psError(PS_ERR_IO, true, "PHU is not FPA, CHIP or CELL.\n");
+                psFree(phdu);
+                psFree(view);
+                return NULL;
+            }
+            psFree(chipName);
+        }
+
+        const char *content = getContent(fileInfo, contents, chip, cell); // The chip:cell:cellType triples
+        if (!content || strlen(content) == 0) {
+            psError(PS_ERR_IO, false, "Unable to get CONTENTS.\n");
+            psFree(phdu);
+            psFree(view);
+            return NULL;
+        }
+        if (processContents(fpa, chip, cell, phdu, level, content, format) < 0) {
+            psError(PS_ERR_IO, false, "Error setting CONTENTS");
+            psFree(phdu);
+            psFree(view);
+            return NULL;
+        }
+        psFree(phdu);
+
+        if (header && !pmConceptsRead(fpa, chip, cell, PM_CONCEPT_SOURCE_PHU, NULL)) {
+            psWarning("Unable to read concepts from PHU.\n");
+        }
+
+        return view;
+    }
+
+    // Case 3: EXTENSIONS=CHIP or EXTENSIONS=CELL.  We have extensions that we iterate through.  The CONTENTS
+    // is a list of extensions.
+    phdu->blankPHU = true;
+    pmChip *chip = NULL;                // The chip of interest
+    pmCell *cell = NULL;                // The cell of interest
+
+    // First, put in the PHU
+    if (strcasecmp(phuType, "FPA") == 0) {
+        addHDUtoFPA(fpa, phdu);
+    } else {
+        // Get the chip
+        psString chipName = phuNameFromHeader("CHIP.NAME", fileInfo, header); // Name of the chip
+        int chipNum = pmFPAFindChip(fpa, chipName); // Chip number
+        if (chipNum == -1) {
+            psError(PS_ERR_IO, true, "Unable to find chip %s in FPA.\n", chipName);
+            psFree(view);
+            return NULL;
+        }
+        chip = fpa->chips->data[chipNum]; // The specified chip
+        view->chip = chipNum;
+
+        if (strcasecmp(phuType, "CHIP") == 0) {
+            addHDUtoChip(chip, phdu);
+        } else if (strcasecmp(phuType, "CELL") == 0) {
+            psString cellName = phuNameFromHeader("CELL.NAME", fileInfo, header); // Name of the cell
+            int cellNum = pmChipFindCell(chip, cellName); // Cell number
+            if (cellNum == -1) {
+                psError(PS_ERR_IO, true, "Unable to find cell %s in chip %s.\n", cellName, chipName);
+                psFree(view);
+                return NULL;
+            }
+            cell = chip->cells->data[cellNum]; // The specified cell
+            view->cell = cellNum;
+            psFree(cellName);
+
+            addHDUtoCell(cell, phdu);
+        } else {
+            psError(PS_ERR_IO, true, "The format of the PHU (%s) is not FPA, CHIP or CELL.\n", phuType);
+            psFree(view);
+            return NULL;
+        }
+        psFree(chipName);
+    }
+    psFree(phdu);
+
+    pmFPALevel level = hduLevel(format);// The level at which to plug in HDU
+
+    // Now go through the contents
+    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL);
+    psMetadataItem *contentsItem = NULL; // Item from contents
+    while ((contentsItem = psMetadataGetAndIncrement(contentsIter))) {
+        const char *extName = contentsItem->name;
+        if (contentsItem->type != PS_DATA_STRING) {
+            psLogMsg(__func__, PS_LOG_WARN, "CONTENTS item %s is not of type STR --- ignored.\n", extName);
+            continue;
+        }
+
+        pmHDU *hdu = pmHDUAlloc(extName); // The extension
+        // Casting to avoid "warning: passing arg 1 of `p_psMemIncrRefCounter' discards qualifiers from
+        // pointer target type"
+        hdu->format = psMemIncrRefCounter((const psPtr)format);
+
+        if (processContents(fpa, chip, cell, hdu, level, contentsItem->data.V, format) < 0) {
+            psError(PS_ERR_IO, false, "Error setting CONTENTS");
+            psFree(view);
+            psFree(hdu);
+            psFree(contentsIter);
+            return NULL;
+        }
+        psFree(hdu);
+    }
+    psFree(contentsIter);
+
+    if (header && !pmConceptsRead(fpa, chip, cell, PM_CONCEPT_SOURCE_PHU, NULL)) {
+        psWarning("Unable to read concepts from PHU.\n");
+    }
+
+    if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_DEFAULTS, true, NULL)) {
+        psWarning("Unable to read concepts from defaults for FPA.  Attempting to "
+                  "proceed anyway.\n");
+    }
+
+    return view;
+}
+
+
+bool pmFPAAddSourceFromView(pmFPA *fpa,   // The FPA
+                            const pmFPAview *phuView, // The view, corresponding to the PHU
+                            const psMetadata *format // Format of file
+                           )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(phuView, false);
+    PS_ASSERT_PTR_NON_NULL(format, false);
+
+    pmFPAview *view = addSource(fpa, phuView, NULL, format);
+    bool status = (view == NULL);
+    psFree (view);
+    return status;
+}
+
+// Add an input file to the FPA
+pmFPAview *pmFPAAddSourceFromHeader(pmFPA *fpa, // The FPA
+                                    psMetadata *phu, // Primary header of file
+                                    const psMetadata *format // Format of file
+                                   )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    PS_ASSERT_PTR_NON_NULL(phu, NULL);
+    PS_ASSERT_PTR_NON_NULL(format, NULL);
+
+    bool mdok = true;                   // Status from metadata lookups
+    psMetadata *fileInfo = psMetadataLookupMD(&mdok, format, "FILE"); // The file information
+    if (!mdok || !fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE in the camera format configuration.\n");
+        return NULL;
+    }
+
+    // Check the name of the FPA
+    psString newFPAname = phuNameFromHeader("FPA.NAME", fileInfo, phu); // New name for the FPA
+    if (!newFPAname) {
+        psError(PS_ERR_IO, true, "Unable to determine FPA.NAME");
+        return NULL;
+    }
+
+    // is FPAname already defined, new name must match it; otherwise, warn the user that something potentially
+    // bad is happening.
+    const char *currentFPAname = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.NAME"); // Current name
+    if (mdok && currentFPAname && strlen(currentFPAname) > 0 && strcmp(currentFPAname, newFPAname) != 0) {
+        psLogMsg(__func__, PS_LOG_WARN, "FPA.NAME for new source (%s) doesn't match FPA.NAME for current "
+                 "fpa (%s).\n", newFPAname, currentFPAname);
+    }
+    psMetadataAddStr(fpa->concepts, PS_LIST_HEAD, "FPA.NAME", PS_META_REPLACE, "Name of FPA", newFPAname);
+    psFree(newFPAname);                 // Drop reference
+
+    return addSource(fpa, NULL, phu, format);
+}
+
+
+// Print out the focal plane structure
+void pmFPAPrint(FILE *fd,               // File descriptor to which to print
+                pmFPA *fpa,             // FPA to print
+                bool header,            // Print headers?
+                bool concepts           // Print concepts?
+               )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa,);
+
+    psTrace("psModules.camera", 1, "FPA:\n");
+    if (fpa->hdu) {
+        pmHDUPrint(fd, fpa->hdu, 2, header);
+    }
+    if (concepts) {
+        psMetadataPrint(fd, fpa->concepts, 2);
+    }
+
+    psArray *chips = fpa->chips;        // Array of chips
+    // Iterate over the FPA
+    for (int i = 0; i < chips->n; i++) {
+        psTrace("psModules.camera", 3, "Chip: %d\n", i);
+        pmChip *chip = chips->data[i]; // The chip
+        if (chip->hdu) {
+            pmHDUPrint(fd, chip->hdu, 4, header);
+        }
+        if (concepts) {
+            psMetadataPrint(fd, chip->concepts, 4);
+        }
+
+        // Iterate over the chip
+        psArray *cells = chip->cells;   // Array of cells
+        for (int j = 0; j < cells->n; j++) {
+            psTrace("psModules.camera", 5, "Cell: %d\n", j);
+            pmCell *cell = cells->data[j]; // The cell
+            if (cell->hdu) {
+                pmHDUPrint(fd, cell->hdu, 6, header);
+            }
+            if (concepts) {
+                psMetadataPrint(fd, cell->concepts, 6);
+            }
+
+            psArray *readouts = cell->readouts; // Array of readouts
+            for (int k = 0; k < readouts->n; k++) {
+                pmReadout *readout = readouts->data[k]; // The readout
+                psTrace("psModules.camera", 6, "Readout %d:\n", k);
+                psTrace("psModules.camera", 7, "row0: %d\n", readout->row0);
+                psTrace("psModules.camera", 7, "col0: %d\n", readout->col0);
+                psImage *image = readout->image; // The image
+                psList *bias = readout->bias; // The list of bias images
+                if (image) {
+                    psTrace("psModules.camera", 7, "Image: [%d:%d,%d:%d] (%dx%d)\n", image->col0, image->col0 +
+                            image->numCols, image->row0, image->row0 + image->numRows, image->numCols,
+                            image->numRows);
+                }
+                if (bias) {
+                    psListIterator *biasIter = psListIteratorAlloc(bias, PS_LIST_HEAD, false); // Iterator
+                    psImage *biasImage = NULL; // Bias image from iteration
+                    while ((biasImage = psListGetAndIncrement(biasIter))) {
+                        psTrace("psModules.camera", 7, "Bias:  [%d:%d,%d:%d] (%dx%d)\n", biasImage->col0,
+                                biasImage->col0 + biasImage->numCols, biasImage->row0,
+                                biasImage->row0 + biasImage->numRows, biasImage->numCols, biasImage->numRows);
+                    }
+                    psFree(biasIter);
+                }
+            } // Iterating over cell
+        } // Iterating over chip
+    } // Iterating over FPA
+
+}
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAConstruct.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAConstruct.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAConstruct.h	(revision 9533)
@@ -0,0 +1,29 @@
+#ifndef PM_FPA_CONSTRUCT_H
+#define PM_FPA_CONSTRUCT_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+#include "pmFPAview.h"
+
+// Construct an FPA instance on the basis of a camera configuration
+pmFPA *pmFPAConstruct(const psMetadata *camera // The camera configuration
+                     );
+
+bool pmFPAAddSourceFromView(pmFPA *fpa,   // The FPA
+                            const pmFPAview *phuView, // The view, corresponding to the PHU
+                            const psMetadata *format // Format of file
+                           );
+
+pmFPAview *pmFPAAddSourceFromHeader(pmFPA *fpa, // The FPA
+                                    psMetadata *phu, // Primary header of file
+                                    const psMetadata *format // Format of file
+                                   );
+
+// Print out the FPA
+void pmFPAPrint(FILE *fd,               // File descriptor to which to print
+                pmFPA *fpa,             // FPA to print
+                bool header,            // Print headers?
+                bool concepts           // Print concepts?
+               );
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPACopy.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPACopy.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPACopy.c	(revision 9533)
@@ -0,0 +1,435 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#include <pslib.h>
+
+#include "pmFPA.h"
+#include "pmFPAUtils.h"
+#include "pmHDU.h"
+#include "pmHDUUtils.h"
+
+#include "pmFPACopy.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Bin a region down by specified factors in x and y
+static void binRegion(psRegion *region, // Region to be binned
+                      int xBin, int yBin// Binning in x and y
+                     )
+{
+    assert(region);
+    assert(xBin > 0);
+    assert(yBin > 0);
+
+    // Want to include the lower bound: 1 binned by 4 --> 0; 3 binned by 4 --> 0; 4 binned by 4 --> 1
+    region->x0 = (int)(region->x0 / xBin);
+    region->y0 = (int)(region->y0 / yBin);
+    // Want to exclude the upper bound: 4 binned by 4 --> 1; 5 binned by 4 --> 2; 7 binned by 4 --> 2
+    region->x1 = (int)((region->x1 + xBin - 1) / xBin);
+    region->y1 = (int)((region->y1 + yBin - 1) / yBin);
+}
+
+// Find the blank (image-less) PHU, given a cell.
+static pmHDU *findBlankPHU(const pmCell *cell// The cell for which to find the PHU
+                          )
+{
+    assert(cell);
+
+    if (cell->hdu && cell->hdu->blankPHU) {
+        return cell->hdu;
+    }
+    pmChip *chip = cell->parent;        // The parent chip
+    if (chip->hdu && chip->hdu->blankPHU) {
+        return chip->hdu;
+    }
+    pmFPA *fpa = chip->parent;  // The parent FPA
+    if (fpa->hdu && fpa->hdu->blankPHU) {
+        return fpa->hdu;
+    }
+
+    return NULL;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static engine functions --- these do all the work.  Actually, cellCopy does all the work; the others
+// merely iterate on the higher-level components.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static bool cellCopy(pmCell *target,     // The target cell
+                     pmCell *source,     // The source cell, to be copied
+                     bool pixels,        // Copy the pixels?
+                     int xBin, int yBin  // (Relative) binning factors in x and y
+                    )
+{
+    assert(target);
+    assert(source);
+    assert(xBin > 0 && yBin > 0);
+
+    psArray *sourceReadouts = source->readouts; // The source readouts
+    int numReadouts = sourceReadouts->n; // Number of readouts copied
+
+    // Copy the value for a concept
+    #define COPY_CONCEPT(TARGET, SOURCE, NAME, TYPE) \
+    psMetadataItem *targetItem = psMetadataLookup(TARGET, NAME); \
+    psMetadataItem *sourceItem = psMetadataLookup(SOURCE, NAME); \
+    targetItem->data.TYPE = sourceItem->data.TYPE;
+
+    // Need to check/change CELL.XPARITY and CELL.YPARITY
+    bool mdok = true;                   // Status of MD lookup
+    bool xFlip = false;                 // Switch parity in x?
+    bool yFlip = false;                 // Switch parity in y?
+    {
+        int xParityTarget = psMetadataLookupS32(&mdok, target->concepts, "CELL.XPARITY"); // Target x parity
+        if (mdok && (xParityTarget == 1 || xParityTarget == -1))
+        {
+            int xParitySource = psMetadataLookupS32(&mdok, source->concepts, "CELL.XPARITY"); // Source parity
+            if (mdok && (xParitySource == 1 || xParitySource == -1) && xParityTarget != xParitySource) {
+                xFlip = true;
+            }
+        } else
+        {
+            // Use the source parity
+            COPY_CONCEPT(target->concepts, source->concepts, "CELL.XPARITY", S32);
+        }
+        int yParityTarget = psMetadataLookupS32(&mdok, target->concepts, "CELL.YPARITY"); // Target y parity
+        if (mdok && (yParityTarget == 1 || yParityTarget == -1))
+        {
+            int yParitySource = psMetadataLookupS32(&mdok, source->concepts, "CELL.YPARITY"); // Source parity
+            if (mdok && (yParitySource == 1 || yParitySource == -1) && yParityTarget != yParitySource) {
+                yFlip = true;
+            }
+        } else
+        {
+            // Use the source parity
+            COPY_CONCEPT(target->concepts, source->concepts, "CELL.YPARITY", S32);
+        }
+        psTrace("psModules.camera", 3, "xFlip: %d; yFlip: %d\n", xFlip, yFlip);
+    }
+
+    if (pixels && (xBin != 1 || yBin != 1)) {
+        psLogMsg(__func__, PS_LOG_WARN, "Unable to copy pixels if binning is set --- copy turned off.\n");
+        pixels = false;
+    }
+
+    // Perform deep copy of the images.  I would prefer *not* to do a deep copy, in the interests of speed (we
+    // still need to do another deep copy into the HDU for when we write out), but this is the only way I can
+    // think of to provide security against copying a cell and then unknowingly changing the source when
+    // manipulating the target.
+    for (int i = 0; i < numReadouts; i++) {
+        pmReadout *sourceReadout = sourceReadouts->data[i]; // The source readout
+        pmReadout *targetReadout = pmReadoutAlloc(target); // The target readout; this adds it to the cell
+
+        // Copy attributes
+        targetReadout->col0 = sourceReadout->col0;
+        targetReadout->row0 = sourceReadout->row0;
+        targetReadout->process = sourceReadout->process;
+        targetReadout->file_exists = sourceReadout->file_exists;
+        targetReadout->data_exists = sourceReadout->data_exists;
+
+        // Copy image
+        if (sourceReadout->image) {
+            if (targetReadout->image) {
+                psFree(targetReadout->image);
+            }
+            if (pixels) {
+                targetReadout->image = psImageFlip(NULL, sourceReadout->image, xFlip, yFlip);
+            } else {
+                psImage *image = sourceReadout->image; // The source image
+                long binnedCols = (image->numCols + xBin - 1) / xBin;
+                long binnedRows = (image->numRows + yBin - 1) / yBin;
+                targetReadout->image = psImageAlloc(binnedCols, binnedRows, image->type.type);
+            }
+        }
+
+        // Copy mask
+        if (sourceReadout->mask) {
+            if (targetReadout->mask) {
+                psFree(targetReadout->mask);
+            }
+            if (pixels) {
+                targetReadout->mask = psImageFlip(NULL, sourceReadout->mask, xFlip, yFlip);
+            } else {
+                psImage *mask = sourceReadout->mask; // The source mask image
+                long binnedCols = (mask->numCols + xBin - 1) / xBin;
+                long binnedRows = (mask->numRows + yBin - 1) / yBin;
+                targetReadout->mask = psImageAlloc(binnedCols, binnedRows, mask->type.type);
+            }
+        }
+
+        // Copy weight
+        if (sourceReadout->weight) {
+            if (targetReadout->weight) {
+                psFree(targetReadout->weight);
+            }
+            if (pixels) {
+                targetReadout->weight = psImageFlip(NULL, sourceReadout->weight, xFlip, yFlip);
+            } else {
+                psImage *weight = sourceReadout->weight; // The source weight image
+                long binnedCols = (weight->numCols + xBin - 1) / xBin;
+                long binnedRows = (weight->numRows + yBin - 1) / yBin;
+                targetReadout->weight = psImageAlloc(binnedCols, binnedRows, weight->type.type);
+            }
+        }
+
+        // Copy bias
+        while (targetReadout->bias->n > 0) {
+            psListRemove(targetReadout->bias, PS_LIST_HEAD);
+        }
+        // Iterate over the biases
+        psListIterator *biasIter = psListIteratorAlloc(sourceReadout->bias, PS_LIST_HEAD, false);
+        psImage *bias = NULL;           // Bias image from iteration
+        while ((bias = psListGetAndIncrement(biasIter))) {
+            psImage *biasCopy;          // Copy of the bias
+            if (pixels) {
+                biasCopy = psImageFlip(NULL, bias, xFlip, yFlip);
+            } else {
+                long binnedCols = (bias->numCols + xBin - 1) / xBin;
+                long binnedRows = (bias->numRows + yBin - 1) / yBin;
+                biasCopy = psImageAlloc(binnedCols, binnedRows, bias->type.type);
+            }
+            psListAdd(targetReadout->bias, PS_LIST_TAIL, biasCopy);
+            psFree(biasCopy);           // Drop reference
+        }
+        psFree(biasIter);
+
+        targetReadout->data_exists = true;
+        psFree(targetReadout);          // Drop reference
+    }
+
+    // Copy the remaining "concepts" over.  Don't copy the TRIMSEC or BIASSEC, since these will be created by
+    // pmHDUGenerate if they don't already exist in the target.  Don't copy the XPARITY or YPARITY, since
+    // we've used those to do the flips.  Don't copy the X0 and Y0 because they are updated below (and are
+    // dependent upon the flips we've done above).
+    psMetadataIterator *conceptsIter = psMetadataIteratorAlloc(source->concepts, PS_LIST_HEAD, NULL);
+    psMetadataItem *conceptItem = NULL; // Item from iteration
+    while ((conceptItem = psMetadataGetAndIncrement(conceptsIter))) {
+        psString name = conceptItem->name; // Name of concept
+        if (strcmp(name, "CELL.TRIMSEC") != 0 && strcmp(name, "CELL.BIASSEC") != 0 &&
+                strcmp(name, "CELL.XPARITY") != 0 && strcmp(name, "CELL.YPARITY") != 0 &&
+                strcmp(name, "CELL.X0") != 0 && strcmp(name, "CELL.Y0") != 0) {
+            psMetadataItem *copy = psMetadataItemCopy(conceptItem); // Copy of the concept
+            psMetadataAddItem(target->concepts, copy, PS_LIST_TAIL, PS_META_REPLACE);
+            psFree(copy);               // Drop reference
+        }
+    }
+    psFree(conceptsIter);
+
+    // Need to update CELL.TRIMSEC and CELL.BIASSEC if we changed the binning and they exist already.
+    if (xBin != 1 || yBin != 1) {
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, target->concepts, "CELL.TRIMSEC"); // The trim section
+        if (mdok && trimsec && !psRegionIsNaN(*trimsec)) {
+            binRegion(trimsec, xBin, yBin);
+        }
+        psList *biassecs = psMetadataLookupPtr(&mdok, target->concepts, "CELL.BIASSEC"); // The bias sections
+        if (mdok && biassecs && biassecs->n > 0) {
+            psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, true); // Iterator
+            psRegion *biassec = NULL;   // Bias section, from iteration
+            while ((biassec = psListGetAndIncrement(biassecsIter))) {
+                if (!psRegionIsNaN(*biassec)) {
+                    binRegion(biassec, xBin, yBin);
+                }
+            }
+            psFree(biassecsIter);
+        }
+    }
+
+    // Need to update CELL.X0 and CELL.Y0 if we flipped
+    if (xFlip) {
+        int xZero = psMetadataLookupS32(NULL, source->concepts, "CELL.X0"); // CELL.X0 from source
+        int xParity = psMetadataLookupS32(NULL, target->concepts, "CELL.XPARITY"); // Parity in x
+        int xBin = psMetadataLookupS32(NULL, source->concepts, "CELL.XBIN"); // CELL.XBIN from source
+        pmReadout *readout = source->readouts->data[0]; // A representative readout
+        psTrace("psModules.camera", 3, "CELL.X0: Before: %d After: %d\n", xZero,
+                xZero - (readout->image->numCols - 1) * xParity * xBin);
+        psTrace("psModules.camera", 9, "(xParity: %d xBin: %d numCols: %d)\n", xParity, xBin, readout->image->numCols);
+        xZero -= (readout->image->numCols - 1) * xParity * xBin; // Change the parity on the X0 position
+        psMetadataItem *newItem = psMetadataLookup(target->concepts, "CELL.X0"); // CELL.X0 from target
+        newItem->data.S32 = xZero;
+    }
+    if (yFlip) {
+        int yZero = psMetadataLookupS32(NULL, source->concepts, "CELL.Y0"); // CELL.Y0 from source
+        int yParity = psMetadataLookupS32(NULL, target->concepts, "CELL.YPARITY"); // Parity in y
+        int yBin = psMetadataLookupS32(NULL, source->concepts, "CELL.YBIN"); // Parity in y
+        pmReadout *readout = source->readouts->data[0]; // A representative readout
+        psTrace("psModules.camera", 3, "CELL.Y0: Before: %d After: %d\n", yZero,
+                yZero - (readout->image->numRows - 1) * yParity * yBin);
+        psTrace("psModules.camera", 9, "(yParity: %d yBin: %d numRows: %d)\n", yParity, yBin, readout->image->numRows);
+        yZero -= (readout->image->numRows - 1) * yParity * yBin; // Change the parity on the Y0 position
+        psMetadataItem *newItem = psMetadataLookup(target->concepts, "CELL.Y0"); // CELL.Y0 from target
+        newItem->data.S32 = yZero;
+    }
+
+    // Update the binning concepts
+    psMetadataItem *binItem = psMetadataLookup(target->concepts, "CELL.XBIN");
+    binItem->data.S32 *= xBin;
+    binItem = psMetadataLookup(target->concepts, "CELL.YBIN");
+    binItem->data.S32 *= yBin;
+
+    // Copy any headers
+    pmHDU *targetHDU = pmHDUFromCell(target); // The target HDU
+    if (targetHDU && !targetHDU->header) {
+        pmHDU *sourceHDU = pmHDUFromCell(source); // The source HDU
+        if (sourceHDU->header) {
+            targetHDU->header = psMetadataCopy(targetHDU->header, sourceHDU->header);
+        }
+    }
+    // Copy the PHU over as well, if required
+    pmHDU *targetPHU = findBlankPHU(target); // The target PHU
+    if (targetPHU && targetPHU != targetHDU && !targetPHU->header) {
+        pmHDU *sourcePHU = findBlankPHU(source); // The source PHU
+        if (sourcePHU->header) {
+            targetPHU->header = psMetadataCopy(targetPHU->header, sourcePHU->header);
+        }
+    }
+
+    target->data_exists = true;
+    return true;
+}
+
+static bool chipCopy(pmChip *target,          // The target chip
+                     pmChip *source,          // The source chip, to be copied
+                     bool pixels,             // Copy the pixels?
+                     int xBin, int yBin       // (Relative) binning factors in x and y
+                    )
+{
+    assert(target);
+    assert(source);
+    assert(xBin > 0);
+    assert(yBin > 0);
+
+    psArray *targetCells = target->cells; // The target cells
+    psArray *sourceCells = source->cells; // The source cells
+    if (targetCells->n != sourceCells->n) {
+        psError(PS_ERR_IO, true, "Number of source cells (%ld) differs from the number of target cells (%ld)\n",
+                sourceCells->n, targetCells->n);
+        return false;
+    }
+
+    bool status = true;                 // Status of copy
+    for (int i = 0; i < targetCells->n; i++) {
+        pmCell *targetCell = targetCells->data[i]; // The target cell
+        const char *cellName = psMetadataLookupStr(NULL, targetCell->concepts, "CELL.NAME"); // Name of cell
+        int cellNum = pmChipFindCell(source, cellName); // Number of cell with that name
+        if (cellNum >= 0) {
+            pmCell *sourceCell = sourceCells->data[cellNum]; // The source cell
+            status &= cellCopy(targetCell, sourceCell, pixels, xBin, yBin);
+        }
+    }
+
+    // Update the concepts
+    psMetadataCopy(target->concepts, source->concepts);
+
+    target->data_exists = true;
+    return status;
+}
+
+static bool fpaCopy(pmFPA *target,            // The target FPA
+                    pmFPA *source,            // The source FPA, to be copied
+                    bool pixels,              // Copy the pixels?
+                    int xBin, int yBin        // (Relative) binning factors in x and y
+                   )
+{
+    assert(target);
+    assert(source);
+    assert(xBin > 0);
+    assert(yBin > 0);
+
+    psArray *targetChips = target->chips; // The target chips
+    psArray *sourceChips = source->chips; // The source chips
+    if (targetChips->n != sourceChips->n) {
+        psError(PS_ERR_IO, true, "Number of source chips (%ld) differs from the number of target chips (%ld)\n",
+                sourceChips->n, targetChips->n);
+        return false;
+    }
+
+    bool status = true;                 // Status of copy
+    for (int i = 0; i < targetChips->n; i++) {
+        pmChip *targetChip = targetChips->data[i]; // The target chip
+        const char *chipName = psMetadataLookupStr(NULL, targetChip->concepts, "CHIP.NAME"); // Name of chip
+        int chipNum = pmFPAFindChip(source, chipName); // Number of chip with that name
+        if (chipNum >= 0) {
+            pmChip *sourceChip = sourceChips->data[chipNum]; // The source chip
+            status &= chipCopy(targetChip, sourceChip, pixels, xBin, yBin);
+        }
+    }
+
+    // Update the concepts
+    psMetadataCopy(target->concepts, source->concepts);
+
+    return status;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmFPACopy(pmFPA *target,            // The target FPA
+               pmFPA *source             // The source FPA, to be copied
+              )
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    return fpaCopy(target, source, true, 1, 1);
+}
+
+bool pmChipCopy(pmChip *target,          // The target chip
+                pmChip *source           // The source chip, to be copied
+               )
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    return chipCopy(target, source, true, 1, 1);
+}
+
+bool pmCellCopy(pmCell *target,          // The target cell
+                pmCell *source           // The source cell, to be copied
+               )
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    return cellCopy(target, source, true, 1, 1);
+}
+
+
+bool pmFPACopyStructure(pmFPA *target,   // The target FPA
+                        pmFPA *source,   // The source FPA, to be copied
+                        int xBin, int yBin // Binning factors in x and y
+                       )
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_INT_POSITIVE(xBin, false);
+    PS_ASSERT_INT_POSITIVE(yBin, false);
+    return fpaCopy(target, source, false, xBin, yBin);
+}
+
+bool pmChipCopyStructure(pmChip *target, // The target chip
+                         pmChip *source, // The source chip, to be copied
+                         int xBin, int yBin // Binning factors in x and y
+                        )
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_INT_POSITIVE(xBin, false);
+    PS_ASSERT_INT_POSITIVE(yBin, false);
+    return chipCopy(target, source, false, xBin, yBin);
+}
+
+bool pmCellCopyStructure(pmCell *target, // The target cell
+                         pmCell *source, // The source cell, to be copied
+                         int xBin, int yBin // Binning factors in x and y
+                        )
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_INT_POSITIVE(xBin, false);
+    PS_ASSERT_INT_POSITIVE(yBin, false);
+    return cellCopy(target, source, false, xBin, yBin);
+}
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPACopy.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPACopy.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPACopy.h	(revision 9533)
@@ -0,0 +1,30 @@
+#ifndef PM_FPA_COPY_H
+#define PM_FPA_COPY_H
+
+// Copy the FPA components, including the pixels
+bool pmFPACopy(pmFPA *target,            // The target FPA
+               pmFPA *source             // The source FPA, to be copied
+              );
+bool pmChipCopy(pmChip *target,          // The target chip
+                pmChip *source           // The source chip, to be copied
+               );
+bool pmCellCopy(pmCell *target,          // The target cell
+                pmCell *source           // The source cell, to be copied
+               );
+
+// Versions that copy the structure and not the pixels; they also allow binning
+bool pmFPACopyStructure(pmFPA *target,   // The target FPA
+                        pmFPA *source,   // The source FPA, to be copied
+                        int xBin, int yBin     // Binning factors in x and y
+                       );
+bool pmChipCopyStructure(pmChip *target, // The target chip
+                         pmChip *source, // The source chip, to be copied
+                         int xBin, int yBin   // Binning factors in x and y
+                        );
+bool pmCellCopyStructure(pmCell *target, // The target cell
+                         pmCell *source, // The source cell, to be copied
+                         int xBin, int yBin // Binning factors in x and y
+                        );
+
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAEnsemble.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAEnsemble.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAEnsemble.c	(revision 9533)
@@ -0,0 +1,301 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmFPA.h"
+#include "pmFPAConstruct.h"
+#include "pmFPAview.h"
+#include "pmFPAEnsemble.h"
+
+
+static void fpaComponentFree(pmFPAComponent *component)
+{
+    psFree(component->fpa);
+    psFree(component->view);
+    return;
+}
+
+pmFPAComponent *pmFPAComponentAlloc(pmFPA *fpa, pmFPAview *view)
+{
+    pmFPAComponent *component = psAlloc(sizeof(pmFPAComponent));
+    psMemSetDeallocator(component, (psFreeFunc)fpaComponentFree);
+    component->fpa = psMemIncrRefCounter(fpa);
+    component->view = psMemIncrRefCounter(view);
+    return component;
+}
+
+bool pmFPAComponentSetExist(pmFPAComponent *component)
+{
+    pmFPA *fpa = component->fpa;        // The FPA of interest
+    pmFPAview *view = component->view;  // The view of interest
+
+    if (view->chip < 0) {
+        // This iterates on all chips
+        return pmFPASetFileStatus(fpa, true);
+    }
+    psArray *chips = fpa->chips;        // Array of chips
+    if (view->chip >= chips->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "FPA view refers to value out of range (%d)\n", view->chip);
+        return false;
+    }
+    pmChip *chip = chips->data[view->chip]; // Chip of interest
+    if (view->cell < 0) {
+        // This iterates on all cells
+        return pmChipSetFileStatus(chip, true);
+    }
+    psArray *cells = chip->cells;       // Array of cells
+    if (view->cell >= cells->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "FPA view refers to value out of range (%d)\n", view->cell);
+        return false;
+    }
+    pmCell *cell = cells->data[view->cell]; // Cell of interest
+    return pmCellSetFileStatus(cell, true);
+}
+
+bool pmFPAComponentCheckExist(pmFPAComponent *component)
+{
+    pmFPA *fpa = component->fpa;        // The FPA of interest
+    pmFPAview *view = component->view;  // The view of interest
+
+    if (view->chip < 0) {
+        // This iterates on all chips
+        return pmFPACheckFileStatus(fpa);
+    }
+    psArray *chips = fpa->chips;        // Array of chips
+    if (view->chip >= chips->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "FPA view refers to value out of range (%d)\n", view->chip);
+        return false;
+    }
+    pmChip *chip = chips->data[view->chip]; // Chip of interest
+    if (view->cell < 0) {
+        // This iterates on all cells
+        return pmChipCheckFileStatus(chip);
+    }
+    psArray *cells = chip->cells;       // Array of cells
+    if (view->cell >= cells->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "FPA view refers to value out of range (%d)\n", view->cell);
+        return false;
+    }
+    pmCell *cell = cells->data[view->cell]; // Cell of interest
+    return pmCellCheckFileStatus(cell);
+}
+
+
+// Move a cell from one chip to another
+static bool cellMove(pmChip *target,    // Target chip
+                     pmChip *source,    // Source chip
+                     int cellNum        // Number of cell
+                    )
+{
+    assert(cellNum > 0);
+    assert(cellNum < source->cells->n);
+
+    pmCell *sourceCell = source->cells->data[cellNum]; // The source cell
+    pmCell *targetCell = target->cells->data[cellNum]; // The target cell
+    // Switch ownership of the cell
+    psFree(targetCell);
+    target->cells->data[cellNum] = sourceCell;
+    source->cells->data[cellNum] = NULL;
+    sourceCell->parent = target;
+
+    return true;
+}
+
+// Move a chip from one FPA to another
+static bool chipMove(pmFPA *target,     // Target FPA
+                     pmFPA *source,     // Source FPA
+                     int chipNum        // Number of chip
+                    )
+{
+    assert(chipNum > 0);
+    assert(chipNum < source->chips->n);
+
+    pmChip *sourceChip = source->chips->data[chipNum]; // The source chip
+    pmChip *targetChip = target->chips->data[chipNum]; // The target chip
+    // Switch ownership of the chip
+    psFree(targetChip);
+    target->chips->data[chipNum] = sourceChip;
+    source->chips->data[chipNum] = NULL;
+    sourceChip->parent = target;
+
+    return true;
+}
+
+
+
+bool pmFPAComponentCopy(pmFPAComponent *target, pmFPA *source)
+{
+    pmFPA *targetFPA = target->fpa;     // The FPA of interest
+    pmFPAview *view = target->view;     // The view of interest
+
+    if (view->chip < 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "This FPA already exists.\n");
+        return false;
+    }
+
+    psArray *targetChips = targetFPA->chips; // Array of chips
+    if (view->chip >= targetChips->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "FPA view refers to value out of range (%d)\n", view->chip);
+        return false;
+    }
+    if (view->cell < 0) {
+        // Move the entire chip over
+        return chipMove(targetFPA, source, view->chip);
+    }
+    pmChip *targetChip = targetChips->data[view->chip]; // Target chip of interest
+    pmChip *sourceChip = source->chips->data[view->chip]; // Source chip of interest
+    psArray *targetCells = targetChip->cells; // Array of cells
+    if (view->cell >= targetCells->n) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "FPA view refers to value out of range (%d)\n", view->cell);
+        return false;
+    }
+    // Move the cell over
+    return cellMove(targetChip, sourceChip, view->cell);
+}
+
+
+
+
+static void fpaEnsembleFree(pmFPAEnsemble *ensemble)
+{
+    psFree(ensemble->fpa);
+    psFree(ensemble->fits);
+    psFree(ensemble->fpaToFits);
+    psFree(ensemble->fitsToFpa);
+    return;
+}
+
+pmFPAEnsemble *pmFPAEnsembleAlloc(psArray *filenames, // Array of file names
+                                  pmConfig *config // Configuration
+                                 )
+{
+    int numFiles = filenames->n;        // Number of files
+    PS_ASSERT_INT_POSITIVE(numFiles, NULL);
+
+    // The objects of interest
+    psArray *fits = psArrayAlloc(numFiles); // Array of file pointers
+    fits->n = numFiles;
+    psMetadata *fpas = psMetadataAlloc(); // The focal plane arrays, indexed by FPA.NAME
+
+    // Translations
+    psArray *fitsToFPA = psArrayAlloc(numFiles); // Array with FPAs for each FITS file
+    fitsToFPA->n = numFiles;
+    psMetadata *fpaToFits = psMetadataAlloc(); // The FITS file for each FPA, indexed by FPA.NAME
+
+    for (int i = 0; i < numFiles; i++) {
+        // Open the file, read the header, get the camera format, generate an FPA structure
+        const char *filename = filenames->data[i]; // File name
+        psFits *fp = psFitsOpen(filename, "r"); // File pointer
+        if (!fp) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to open %s --- ignored.\n", filename);
+            continue;
+        }
+        psMetadata *header = psFitsReadHeader(NULL, fp); // Primary header from the FITS file
+        if (!header) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to read header for %s --- ignored.\n", filename);
+            psFitsClose(fp);
+            continue;
+        }
+        psMetadata *format = pmConfigCameraFormatFromHeader(config, header);
+        if (!format) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to determine camera format for %s --- ignored.\n",
+                     filename);
+            psFree(header);
+            psFitsClose(fp);
+            continue;
+        }
+        // Construct camera in preparation for reading
+        pmFPA *fpa = pmFPAConstruct(config->camera); // An FPA to contain only this file
+        if (!fpa) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to construct FPA for %s --- ignored.\n", filename);
+            psFree(format);
+            psFree(header);
+            psFitsClose(fp);
+            continue;
+        }
+        pmFPAview *view = pmFPAAddSourceFromHeader(fpa, header, format); // View corresponding to the PHU
+        if (!view) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to add file to FPA for %s --- ignored.\n", filename);
+            psFree(fpa);
+            psFree(format);
+            psFree(header);
+            psFitsClose(fp);
+            continue;
+        }
+
+        // Put this into any existing FPA
+        bool mdok;                      // Status of MD lookup
+        const char *fpaName = psMetadataLookupStr(&mdok, fpa->concepts, "FPA.NAME"); // Name of the FPA
+        if (!mdok || !fpaName || strlen(fpaName) == 0) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to find FPA.NAME for %s --- ignored.\n", filename);
+            psFree(view);
+            psFree(fpa);
+            psFree(format);
+            psFree(header);
+            psFitsClose(fp);
+            continue;
+        }
+
+        psTrace("psModules.camera", 3, "%s --> FPA: %s, chip: %d, cell: %d\n", filename, fpaName,
+                view->chip, view->cell);
+
+        pmFPAComponent *component;      // FPA component for the new file
+        psMetadataItem *check = psMetadataLookup(fpas, fpaName); // Check for an existing FPA with same name
+        if (!check) {
+            // Add in the new FPA
+            psTrace("psModules.camera", 5, "New FPA.\n");
+            component = pmFPAComponentAlloc(fpa, view);
+            psMetadataAddPtr(fpas, PS_LIST_TAIL, fpaName, PS_DATA_UNKNOWN, NULL, component->fpa);
+        } else {
+            // Need to put the appropriate element of the new FPA in the old one.
+            psTrace("psModules.camera", 5, "Adding to extant FPA.\n");
+            pmFPA *oldFPA = check->data.V; // The existing FPA
+            component = pmFPAComponentAlloc(oldFPA, view);
+            // Check that it doesn't already exist
+            if (pmFPAComponentCheckExist(component)) {
+                psLogMsg(__func__, PS_LOG_WARN, "The component for FPA %s in file %s already exists --- "
+                         "ignored.\n", fpaName, filename);
+                psFree(component);
+                psFree(view);
+                psFree(fpa);
+                psFree(format);
+                psFree(header);
+                psFitsClose(fp);
+                continue;
+            }
+
+            if (!pmFPAComponentCopy(component, fpa)) {
+                psLogMsg(__func__, PS_LOG_WARN, "Unable to copy FPA component for %s --- ignored.\n",
+                         filename);
+                psFree(component);
+                psFree(view);
+                psFree(fpa);
+                psFree(format);
+                psFree(header);
+                psFitsClose(fp);
+                continue;
+            }
+        }
+        pmFPAComponentSetExist(component);
+
+        fits->data[i] = fp;
+        // Fill in the translations
+        fitsToFPA->data[i] = component;
+        psMetadataAddS32(fpaToFits, PS_LIST_TAIL, fpaName, PS_META_DUPLICATE_OK, NULL, i);
+    }
+
+    pmFPAEnsemble *ensemble = psAlloc(sizeof(pmFPAEnsemble)); // The ensemble, to be returned
+    psMemSetDeallocator(ensemble, (psFreeFunc)fpaEnsembleFree);
+
+    ensemble->fits = fits;
+    ensemble->fpa = fpas;
+    ensemble->fitsToFpa = fitsToFPA;
+    ensemble->fpaToFits = fpaToFits;
+
+    return ensemble;
+}
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAEnsemble.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAEnsemble.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAEnsemble.h	(revision 9533)
@@ -0,0 +1,41 @@
+#ifndef PM_FPA_ENSEMBLE_H
+#define PM_FPA_ENSEMBLE_H
+
+// An ensemble of FPAs: given a bunch of filenames, we want to generate the FPAs that those files represent,
+// along with the links from one to the other, and the other to the one.
+typedef struct
+{
+    // The data
+    psMetadata *fpa;                    // Focal plane arrays, indexed by FPA.NAME
+    psArray *fits;                      // FITS file pointers
+    // The translations
+    psMetadata *fpaToFits;              // Given an FPA.NAME, what is the FITS file?
+    psArray *fitsToFpa;                 // Given a FITS file, what is the FPA?
+}
+pmFPAEnsemble;
+
+pmFPAEnsemble *pmFPAEnsembleAlloc(psArray *filenames, // Array of file names
+                                  pmConfig *config // Configuration
+                                 );
+
+// A component of an FPA, represented by the FPA and a view.
+typedef struct
+{
+    pmFPA *fpa;                         // Focal plane array
+    pmFPAview *view;                    // View corresponding to the PHU
+}
+pmFPAComponent;
+
+pmFPAComponent *pmFPAComponentAlloc(pmFPA *fpa, pmFPAview *view);
+
+// Set the file_exist flag for this FPA component
+bool pmFPAComponentSetExist(pmFPAComponent *component);
+
+// Check the file_exist flags for this FPA component
+bool pmFPAComponentCheckExist(pmFPAComponent *component);
+
+// Copy a component of the source FPA to the target
+bool pmFPAComponentCopy(pmFPAComponent *target, pmFPA *source);
+
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAHeader.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAHeader.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAHeader.c	(revision 9533)
@@ -0,0 +1,81 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <pslib.h>
+#include "pmFPA.h"
+#include "pmHDU.h"
+#include "pmConcepts.h"
+#include "pmFPAHeader.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmCellReadHeader(pmCell *cell,     // Cell for which to read header
+                      psFits *fits      // FITS file handle
+                     )
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    if (!cell->hdu) {
+        return pmChipReadHeader(cell->parent, fits);
+    }
+    if (!pmHDUReadHeader(cell->hdu, fits)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell.\n");
+        return false;
+    }
+
+    return pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, false, NULL);
+}
+
+
+bool pmChipReadHeader(pmChip *chip,     // Chip for which to read header
+                      psFits *fits      // FITS file handle
+                     )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    if (!chip->hdu) {
+        return pmFPAReadHeader(chip->parent, fits);
+    }
+    if (!pmHDUReadHeader(chip->hdu, fits)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell.\n");
+        return false;
+    }
+
+    if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER, true, true, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts for chip.\n");
+        return false;
+    }
+
+    return true;
+}
+
+
+bool pmFPAReadHeader(pmFPA *fpa,        // FPA for which to read header
+                     psFits *fits       // FITS file handle
+                    )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    if (!fpa->hdu) {
+        return false;
+    }
+    if (!pmHDUReadHeader(fpa->hdu, fits)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell.\n");
+        return false;
+    }
+
+    if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read concepts for FPA.\n");
+        return false;
+    }
+
+    return true;
+}
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAHeader.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAHeader.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAHeader.h	(revision 9533)
@@ -0,0 +1,14 @@
+#ifndef PM_FPA_HEADER_H
+#define PM_FPA_HEADER_H
+
+bool pmCellReadHeader(pmCell *cell,     // Cell for which to read header
+                      psFits *fits      // FITS file handle
+                     );
+bool pmChipReadHeader(pmChip *chip,     // Chip for which to read header
+                      psFits *fits      // FITS file handle
+                     );
+bool pmFPAReadHeader(pmFPA *fpa,        // FPA for which to read header
+                     psFits *fits       // FITS file handle
+                    );
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAMaskWeight.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAMaskWeight.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAMaskWeight.c	(revision 9533)
@@ -0,0 +1,295 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+
+#include <pslib.h>
+#include "pmFPA.h"
+#include "pmHDU.h"
+#include "pmHDUUtils.h"
+#include "pmHDUGenerate.h"
+#include "pmFPAMaskWeight.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static (private) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Create the parent mask images that reside in the HDU
+static void createParentMasks(pmHDU *hdu // The HDU for which to create
+                             )
+{
+    assert(hdu);
+    assert(hdu->images);
+
+    // Generate the parent mask images
+    psArray *images = hdu->images;      // Array of images
+    psArray *masks = hdu->masks;        // Array of masks
+    if (!masks) {
+        masks = psArrayAlloc(images->n);
+        masks->n = images->n;
+        hdu->masks = masks;
+    }
+
+    for (long i = 0; i < images->n; i++) {
+        psImage *image = images->data[i]; // The image for this readout
+        if (!image || masks->data[i]) {
+            continue;
+        }
+        masks->data[i] = psImageAlloc(image->numCols, image->numRows, PS_TYPE_U8);
+    }
+
+    return;
+}
+
+// Create the parent weight images that reside in the HDU
+static void createParentWeights(pmHDU *hdu // The HDU for which to create
+                               )
+{
+    assert(hdu);
+    assert(hdu->images);
+
+    // Generate the parent mask images
+    psArray *images = hdu->images;      // Array of images
+    psArray *weights = hdu->weights;    // Array of weight images
+    if (!weights) {
+        weights = psArrayAlloc(images->n);
+        weights->n = images->n;
+        hdu->weights = weights;
+    }
+
+    for (long i = 0; i < images->n; i++) {
+        psImage *image = images->data[i]; // The image for this readout
+        if (!image || weights->data[i]) {
+            continue;
+        }
+        weights->data[i] = psImageAlloc(image->numCols, image->numRows, PS_TYPE_F32);
+    }
+
+    return;
+}
+
+// Identify a readout within the HDU, on the basis of the image pointer.
+// This is a little dirty, but hopefully should work....
+static long identifyReadout(pmHDU *hdu, // The HDU containing the readouts
+                            pmReadout *readout // The readout to be identified
+                           )
+{
+    assert(hdu);
+    assert(readout);
+
+    long index = -1;                    // Index of the readout
+    for (long i = 0; i < hdu->images->n && index == -1; i++) {
+        if (hdu->images->data[i] == readout->image->parent) {
+            index = i;
+        }
+    }
+
+    return index;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmReadoutSetMask(pmReadout *readout// Readout for which to set mask
+                     )
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_IMAGE_NON_NULL(readout->image, false);
+
+    pmCell *cell = readout->parent;     // The parent cell
+    bool mdok = true;                   // Status of MD lookup
+
+    // Get the "concepts" of interest
+    float saturation = psMetadataLookupF32(&mdok, cell->concepts, "CELL.SATURATION"); // Saturation level
+    if (!mdok || isnan(saturation)) {
+        psError(PS_ERR_IO, true, "CELL.SATURATION is not set --- unable to set mask.\n");
+        return false;
+    }
+    float bad = psMetadataLookupF32(&mdok, cell->concepts, "CELL.BAD"); // Bad level
+    if (!mdok || isnan(bad)) {
+        psError(PS_ERR_IO, true, "CELL.BAD is not set --- unable to set mask.\n");
+        return false;
+    }
+    psTrace("psModules.camera", 5, "Saturation: %f, bad: %f\n", saturation, bad);
+
+
+    // Set up the mask
+    psImage *image = readout->image;    // The image pixels
+    if (!readout->mask) {
+        // Generate a (throwaway) mask image, if required
+        readout->mask = psImageAlloc(image->numCols, image->numRows, PS_TYPE_U8);
+    }
+    psImage *mask = readout->mask;      // The mask pixels
+
+    // Dereference pointers for speed
+    psF32 **imageData = image->data.F32;// The image
+    psU8  **maskData  = mask->data.U8;  // The mask
+
+    for (int i = 0; i < image->numRows; i++) {
+        for (int j = 0; j < image->numCols; j++) {
+            if (imageData[i][j] >= saturation) {
+                maskData[i][j] |= PM_MASK_SAT;
+            }
+            if (imageData[i][j] <= bad) {
+                maskData[i][j] |= PM_MASK_BAD;
+            }
+        }
+    }
+
+    return true;
+}
+
+bool pmReadoutGenerateMask(pmReadout *readout // Readout for which to generate mask
+                          )
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    pmCell *cell = readout->parent;     // The parent cell
+    bool mdok = true;                   // Status of MD lookup
+
+    // Create the mask image if required
+    if (!readout->mask) {
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (!mdok || psRegionIsNaN(*trimsec)) {
+            psError(PS_ERR_IO, true, "CELL.TRIMSEC is not set --- unable to set mask.\n");
+            return false;
+        }
+
+        pmHDU *hdu = pmHDUFromCell(cell);   // The HDU containing the cell's pixels
+        PS_ASSERT_PTR_NON_NULL(hdu, false);
+        if (!hdu->images && !pmHDUGenerateForCell(cell)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate HDU for cell.\n");
+            return false;
+        }
+
+        createParentMasks(hdu);
+
+        // Need to identify which readout we're working with....
+        long index = identifyReadout(hdu, readout); // Index of the readout
+        if (index == -1) {
+            psError(PS_ERR_UNKNOWN, true, "Unable to identify readout image in HDU.\n");
+            return false;
+        }
+
+        psImage *mask = psImageSubset(hdu->masks->data[index], *trimsec); // The mask pixels
+        if (!mask) {
+            psString trimsecString = psRegionToString(*trimsec);
+            psError(PS_ERR_UNKNOWN, false, "Unable to set mask from HDU with trimsec: %s.\n", trimsecString);
+            psFree(trimsecString);
+            return false;
+        }
+        psImageInit(mask, 0);
+        assert (readout->mask == NULL); // or else this is a memory leak.
+        readout->mask = mask;
+    }
+
+    return pmReadoutSetMask(readout);
+}
+
+bool pmReadoutSetWeight(pmReadout *readout // Readout for which to set weight
+                       )
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    pmCell *cell = readout->parent;     // The parent cell
+    bool mdok = true;                   // Status of MD lookup
+
+    // Get the "concepts" of interest
+    float gain = psMetadataLookupF32(&mdok, cell->concepts, "CELL.GAIN"); // Cell gain
+    if (!mdok || isnan(gain)) {
+        psError(PS_ERR_IO, true, "CELL.GAIN is not set --- unable to set weight.\n");
+        return false;
+    }
+    float readnoise = psMetadataLookupF32(&mdok, cell->concepts, "CELL.READNOISE"); // Cell read noise
+    if (!mdok || isnan(readnoise)) {
+        psError(PS_ERR_IO, true, "CELL.READNOISE is not set --- unable to set weight.\n");
+        return false;
+    }
+
+    // Set weight image to the variance in ADU = f/g + rn^2
+    psImage *image = readout->image;    // The image pixels
+    readout->weight = (psImage*)psBinaryOp(readout->weight, image, "/", psScalarAlloc(gain, PS_TYPE_F32));
+    readout->weight = (psImage*)psBinaryOp(readout->weight, readout->weight, "+",
+                                           psScalarAlloc(readnoise*readnoise/gain/gain, PS_TYPE_F32));
+
+    return true;
+}
+
+bool pmReadoutGenerateWeight(pmReadout *readout // Readout for which to generate weight
+                            )
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    pmCell *cell = readout->parent;     // The parent cell
+    bool mdok = true;                   // Status of MD lookup
+
+    // Create the weight image if required
+    if (!readout->weight) {
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (!mdok || psRegionIsNaN(*trimsec)) {
+            psError(PS_ERR_IO, true, "CELL.TRIMSEC is not set --- unable to set weight.\n");
+            return false;
+        }
+
+        pmHDU *hdu = pmHDUFromCell(cell);   // The HDU containing the cell's pixels
+        PS_ASSERT_PTR_NON_NULL(hdu, false);
+        if (!hdu->images && !pmHDUGenerateForCell(cell)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to generate HDU for cell.\n");
+            return false;
+        }
+
+        createParentWeights(hdu);
+
+        // Need to identify which readout we're working with....
+        long index = identifyReadout(hdu, readout); // Index of the readout
+        if (index == -1) {
+            psError(PS_ERR_UNKNOWN, true, "Unable to identify readout image in HDU.\n");
+            return false;
+        }
+
+        psImage *weight = psImageSubset(hdu->weights->data[index], *trimsec); // The weight pixels
+        if (!weight) {
+            psString trimsecString = psRegionToString(*trimsec);
+            psError(PS_ERR_UNKNOWN, false, "Unable to set weight from HDU with trimsec: %s.\n",
+                    trimsecString);
+            psFree(trimsecString);
+            return false;
+        }
+        psImageInit(weight, 0);
+        readout->weight = weight;
+    }
+
+    return pmReadoutSetWeight(readout);
+}
+
+bool pmCellGenerateMaskWeight(pmCell *cell // Cell for which to set weights
+                             )
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+
+    bool success = true;                // Was everything successful?
+    psArray *readouts = cell->readouts; // Array of readouts
+    for (int i = 0; i < readouts->n; i++) {
+        pmReadout *readout = readouts->data[i]; // The readout
+        success |= pmReadoutGenerateMask(readout);
+        success |= pmReadoutGenerateWeight(readout);
+    }
+
+    return success;
+}
+
+bool pmReadoutGenerateMaskWeight(pmReadout *readout // Readout for which to set mask and weights
+                                )
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+
+    bool success = true;                // Was everything successful?
+
+    success |= pmReadoutGenerateMask(readout);
+    success |= pmReadoutGenerateWeight(readout);
+
+    return success;
+}
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAMaskWeight.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAMaskWeight.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAMaskWeight.h	(revision 9533)
@@ -0,0 +1,44 @@
+#ifndef PM_READOUT_MASK_WEIGHT_H
+#define PM_READOUT_MASK_WEIGHT_H
+
+#include "pmFPA.h"
+
+// these defines are necessary to yield 8-bit results (use instead of ~)
+#define NOT_U8(A)(UCHAR_MAX-(A))
+#define NOT_U16(A)(USHORT_MAX-(A))
+
+/** Mask values */
+typedef enum {
+    PM_MASK_CLEAR   = 0x00,   ///< The pixel is not masked
+    PM_MASK_TRAP    = 0x01,   ///< The pixel is a charge trap.
+    PM_MASK_BADCOL  = 0x02,   ///< The pixel is a bad column.
+    PM_MASK_SAT     = 0x04,   ///< The pixel is saturated.
+    PM_MASK_BAD     = 0x08,   ///< The pixel is low
+    PM_MASK_FLAT    = 0x10,   ///< The pixel is non-positive in the flat-field.
+    PM_MASK_MARK    = 0x20,   ///< The pixel is marked as temporarily ignored
+    PM_MASK_EXT1    = 0x40,   ///< This mask value is not used
+    PM_MASK_EXT2    = 0x80,   ///< This mask value is not used
+} pmMaskValue;
+
+// These functions set an extant mask/weight (or create a throwaway one).
+// The throwaway case is intended for when the user is iterating using pmReadoutReadNext, in which case
+// the HDU can't be generated
+bool pmReadoutSetMask(pmReadout *readout // Readout for which to set mask
+                     );
+bool pmReadoutSetWeight(pmReadout *readout // Readout for which to set weight
+                       );
+
+// These functions generate a mask/weight suitable for output (complete with HDU entry) and then set them.
+bool pmReadoutGenerateMask(pmReadout *readout // Readout for which to generate mask
+                          );
+bool pmReadoutGenerateWeight(pmReadout *readout // Readout for which to generate weight
+                            );
+
+// These functions are conveniences for pmReadoutGenerateMask and pmReadoutGenerateWeight.
+bool pmCellGenerateMaskWeight(pmCell *cell // Cell for which to generate mask and weights
+                             );
+bool pmReadoutGenerateMaskWeight(pmReadout *readout // Readout for which to generate mask and weights
+                                );
+
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAMosaic.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAMosaic.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAMosaic.c	(revision 9533)
@@ -0,0 +1,1197 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <pslib.h>
+#include "pmFPA.h"
+#include "pmHDU.h"
+#include "pmConceptsAverage.h"
+#include "pmHDUUtils.h"
+#include "pmFPAMosaic.h"
+#include "pmConfig.h"
+
+#define CELL_LIST_BUFFER 50             // Buffer size for cell lists
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static (private) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Do two regions overlap?
+#define REGIONS_OVERLAP(region1, region2) \
+((region1->x0 > region2->x0 && region1->x0 < region2->x1) || \
+ (region1->x1 > region2->x0 && region1->x1 < region2->x1) || \
+ (region1->y0 > region2->y0 && region1->y0 < region2->y1) || \
+ (region1->y1 > region2->y0 && region1->y1 < region2->y1))
+
+// Compare a value with a maximum and minimum
+#define COMPARE(value,min,max) \
+if ((value) < (min)) { \
+    (min) = (value); \
+} \
+if ((value) > (max)) { \
+    (max) = (value); \
+}
+
+// Update a concept to the assumed value
+#define FIX_CONCEPT(SOURCE, NAME, TYPE, VALUE) \
+psMetadataItem *item = psMetadataLookup(SOURCE, NAME); \
+item->data.TYPE = VALUE;
+
+// Get the bounds for an chip's pixels on the HDU
+static bool chipBounds(psRegion *bounds, // The bounds for the chip
+                       const pmChip *chip // The chip to examine for contiguity
+                      )
+{
+    assert(chip);
+
+    psArray *cells = chip->cells;       // The array of cells
+    bool mdok = true;                   // Status of MD lookup
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+            psError(PS_ERR_UNKNOWN, true, "CELL.TRIMSEC hasn't been set for cell %d.\n", i);
+            return false;
+        }
+
+        if (trimsec->x0 < bounds->x0) {
+            bounds->x0 = trimsec->x0;
+        }
+        if (trimsec->x1 > bounds->x1) {
+            bounds->x1 = trimsec->x1;
+        }
+        if (trimsec->y0 < bounds->y0) {
+            bounds->y0 = trimsec->y0;
+        }
+        if (trimsec->y1 > bounds->y1) {
+            bounds->y1 = trimsec->y1;
+        }
+    }
+
+    return true;
+}
+
+// Make sure the TRIMSEC doesn't overlap with the established image bounds
+static bool chipContiguousTrimsec(psRegion *bounds, // The bounds of the image, altered if primary==true
+                                  const pmChip *chip // The chip to examine for contiguity
+                                 )
+{
+    assert(bounds);
+    assert(chip);
+
+    psArray *cells = chip->cells;       // The array of cells
+    bool mdok = true;                   // Status of MD lookup
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+            psError(PS_ERR_UNKNOWN, true, "CELL.TRIMSEC hasn't been set for cell %d.\n", i);
+            return false;
+        }
+
+        if (REGIONS_OVERLAP(trimsec, bounds)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+// Make sure the BIASSEC doesn't overlap with the established image bounds
+static bool chipContiguousBiassec(psRegion *bounds, // The bounds of the image, altered if primary==true
+                                  const pmChip *chip // The chip to examine for contiguity
+                                 )
+{
+    assert(bounds);
+    assert(chip);
+
+    // Check that the biases don't get in the way
+    psArray *cells = chip->cells;       // The array of cells
+    bool mdok = true;                   // Status of MD lookup
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+        psList *biassecs = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.BIASSEC"); // Bias sections
+        if (!mdok || !biassecs) {
+            psError(PS_ERR_UNKNOWN, true, "CELL.BIASSEC hasn't been set for cell %d.\n", i);
+            return false;
+        }
+        if (biassecs->n == 0) {
+            // No point allocating an iterator if there's nothing there to iterate on
+            continue;
+        }
+        psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+        psRegion *biassec = NULL;       // Bias section from iteration
+        while ((biassec = psListGetAndIncrement(biassecsIter))) {
+            if (psRegionIsNaN(*biassec)) {
+                continue;
+            }
+            if (REGIONS_OVERLAP(biassec, bounds)) {
+                psFree(biassecsIter);
+                return false;
+            }
+        }
+        psFree(biassecsIter);
+    }
+
+    // If we've gotten this far, everything is fine.
+    return true;
+}
+
+// Are the pixels for the FPA contiguous on the HDU?
+// Work this out by examining all the CELL.TRIMSEC and CELL.BIASSEC regions for the component cells
+static bool fpaContiguous(psRegion *bounds, // The bounds of the image, returned
+                          const pmFPA *fpa // The FPA to examine for contiguity
+                         )
+{
+    assert(bounds);
+    assert(fpa);
+
+    *bounds = psRegionSet(INFINITY, 0, INFINITY, 0);
+
+    // Get the size of the pixels on the HDU
+    psArray *chips = fpa->chips;        // The array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        if (!chipBounds(bounds, chip)) {
+            return false;
+        }
+    }
+
+    // Make sure the bias regions don't get in the way of the HDU
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        if (!chipContiguousBiassec(bounds, chip)) {
+            return false;
+        }
+    }
+
+    // If we got through it all, they must all be contiguous
+    return true;
+}
+
+
+
+// Check a cell for niceness in the parity and binning
+static bool niceCellParityBinning(int *xBin, int *yBin, // Binning for cell, to be returned
+                                  const pmCell *cell // Cell to check for niceness
+                                 )
+{
+    assert(xBin);
+    assert(yBin);
+    assert(cell);
+
+    // A "nice" cell must have only a single readout
+    if (cell->readouts->n != 1) {
+        return false;
+    }
+
+    // A "nice" cell must have parity == 1
+    bool mdok = true;                   // Status of MD lookup
+    int xParity = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XPARITY"); // Parity in x
+    if (!mdok || xParity == 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.XPARITY hasn't been set for cell.\n");
+        return false;
+    }
+    if (xParity != 1) {
+        return false;
+    }
+    int yParity = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YPARITY"); // Parity in y
+    if (!mdok || yParity == 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.YPARITY hasn't been set for cell.\n");
+        return false;
+    }
+    if (yParity != 1) {
+        return false;
+    }
+
+    // A "nice" cell must have consistent binning
+    int xBinCell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XBIN"); // Binning in x
+    if (!mdok || xBin <= 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.XPARITY hasn't been set for cell.\n");
+        return false;
+    }
+    int yBinCell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YBIN"); // Binning in y
+    if (!mdok || yBin <= 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.YPARITY hasn't been set for cell.\n");
+        return false;
+    }
+    if (*xBin == 0 || *yBin == 0) {
+        *xBin = xBinCell;
+        *yBin = yBinCell;
+    } else if (xBinCell != *xBin || yBinCell != *yBin) {
+        return false;
+    }
+
+    return true;
+}
+
+
+// Check a cell for niceness in the boundaries
+static bool niceCellBounds(const pmCell *cell, // Cell to check for niceness
+                           const psRegion *imageBounds // Bounds of the image on the HDU
+                          )
+{
+    // A "nice" cell must have the (0,0) pixel at CELL.X0,CELL.Y0
+    bool mdok = true;                   // Status of MD lookup
+    int x0 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.X0"); // Position of (0,0) on chip
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.X0 hasn't been set for cell.\n");
+        return false;
+    }
+    int y0 = psMetadataLookupS32(&mdok, cell->concepts, "CELL.Y0"); // Position of (0,0) on chip
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.Y0 hasn't been set for cell.\n");
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[0]; // A representative readout
+    if (!readout) {
+        return false;                   // Nothing here
+    }
+    if (x0 != readout->col0 + readout->image->col0 - (int)imageBounds->x0 ||
+            y0 != readout->row0 + readout->image->row0 - (int)imageBounds->y0) {
+        psTrace("psModules.camera", 5, "CELL.X0,Y0 don't match: %d,%d vs %d,%d\n", x0, y0,
+                readout->col0 + readout->image->col0 - (int)imageBounds->x0,
+                readout->row0 + readout->image->row0 - (int)imageBounds->y0);
+        return false;
+    }
+
+    return true;
+}
+
+
+// Is the chip "nice"?  If so, return the region containing the chip pixels
+static psRegion *niceChip(int *xBinChip, int *yBinChip, // Binning for chip, to be returned
+                          const pmChip *chip // Chip to examine for "niceness".
+                         )
+{
+    assert(xBinChip);
+    assert(yBinChip);
+    assert(chip);
+
+    // Check that we've got the HDU in the chip or the FPA
+    if ((!chip->hdu || !chip->hdu->images) && (!chip->parent->hdu || !chip->parent->hdu->images)) {
+        return NULL;
+    }
+
+    // Check parity and binning for component cells
+    *xBinChip = 0;
+    *yBinChip = 0;
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i]; // The cell of interest
+        if (!niceCellParityBinning(xBinChip, yBinChip, cell)) {
+            return NULL;
+        }
+    }
+
+    // Now check that the pixels are all contiguous
+    psRegion *imageBounds = psRegionAlloc(INFINITY, 0, INFINITY, 0); // Bound of image on HDU
+    if (!chipBounds(imageBounds, chip) || !chipContiguousBiassec(imageBounds, chip)) {
+        psTrace("psModules.camera", 5, "Image isn't contiguous.\n");
+        psFree(imageBounds);
+        return NULL;
+    }
+
+    psString region = psRegionToString(*imageBounds);
+    psTrace("psModules.camera", 7, "Image bounds: %s\n", region);
+    psFree(region);
+
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i]; // The cell of interest
+        if (!niceCellBounds(cell, imageBounds)) {
+            psFree(imageBounds);
+            return NULL;
+        }
+    }
+
+    // Need to check all the other chips if the HDU is in the FPA
+    pmFPA *fpa = chip->parent;          // The parent FPA
+    if (fpa->hdu && fpa->hdu->images) {
+        psArray *chips = fpa->chips;    // Array of chips
+        for (int i = 0; i < chips->n; i++) {
+            pmChip *testChip = chips->data[i]; // The chip of interest
+            if (testChip == chip) {
+                // Already done this one
+                continue;
+            }
+            if (!chipContiguousTrimsec(imageBounds, testChip) ||
+                    !chipContiguousBiassec(imageBounds, testChip)) {
+                psTrace("psModules.camera", 5, "Image isn't contiguous.\n");
+                psFree(imageBounds);
+                return NULL;
+            }
+        }
+    }
+
+    return imageBounds;
+}
+
+// Is the FPA "nice"?  If so, return the region containing the FPA pixels
+static psRegion *niceFPA(int *xBinFPA, int *yBinFPA, // Binning for FPA, to be returned
+                         const pmFPA *fpa  // FPA to examine for "niceness".
+                        )
+{
+    assert(xBinFPA);
+    assert(yBinFPA);
+    assert(fpa);
+
+    // Check that we've got the HDU in the chip or the FPA
+    if (!fpa->hdu || !fpa->hdu->images) {
+        return NULL;
+    }
+
+    // Check parity and binning for component cells
+    *xBinFPA = 0;
+    *yBinFPA = 0;
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i]; // The chip of interest
+        for (int j = 0; i < chip->cells->n; i++) {
+            pmCell *cell = chip->cells->data[j]; // The cell of interest
+            if (!niceCellParityBinning(xBinFPA, yBinFPA, cell)) {
+                return NULL;
+            }
+        }
+    }
+
+    // Now check that the pixels are all contiguous
+    psRegion *imageBounds = psRegionAlloc(0, 0, 0, 0); // Bound of image on HDU
+    if (!fpaContiguous(imageBounds, fpa)) {
+        psTrace("psModules.camera", 5, "Image isn't contiguous.\n");
+        psFree(imageBounds);
+        return NULL;
+    }
+
+    psString region = psRegionToString(*imageBounds);
+    psTrace("psModules.camera", 7, "Image bounds: %s\n", region);
+    psFree(region);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i]; // The chip of interest
+        for (int j = 0; i < chip->cells->n; i++) {
+            pmCell *cell = chip->cells->data[j]; // The cell of interest
+            if (!niceCellBounds(cell, imageBounds)) {
+                psFree(imageBounds);
+                return NULL;
+            }
+        }
+    }
+
+    return imageBounds;
+}
+
+// Mosaic multiple images, with flips, binning and offsets
+static psImage *imageMosaic(const psArray *source, // Images to splice in
+                            const psVector *xFlip, const psVector *yFlip, // Need to flip x and y?
+                            const psVector *xBinSource, // Binning in x of source images
+                            const psVector *yBinSource, // Binning in y of source images
+                            int xBinTarget, int yBinTarget, // Binning in x and y of target images
+                            const psVector *x0, const psVector *y0 // Offsets for source images on target
+                           )
+{
+    assert(source);
+    assert(xFlip && xFlip->type.type == PS_TYPE_U8);
+    assert(yFlip && yFlip->type.type == PS_TYPE_U8);
+    assert(xBinSource && xBinSource->type.type == PS_TYPE_S32);
+    assert(yBinSource && yBinSource->type.type == PS_TYPE_S32);
+    assert(x0 && x0->type.type == PS_TYPE_S32);
+    assert(y0 && y0->type.type == PS_TYPE_S32);
+    assert(xFlip->n == source->n);
+    assert(yFlip->n == source->n);
+    assert(xBinSource->n == source->n);
+    assert(yBinSource->n == source->n);
+    assert(x0->n == source->n);
+    assert(y0->n == source->n);
+
+    if (source->n == 0) {
+        return NULL;
+    }
+
+    // Get the maximum extent of the mosaic image
+    int xMin = INT_MAX;
+    int xMax = - INT_MAX;
+    int yMin = INT_MAX;
+    int yMax = - INT_MAX;
+    psElemType type = 0;
+    int numImages = 0;                  // Number of images
+    psTrace("psModules.camera", 3, "Mosaicking %ld cells.\n", source->n);
+    for (int i = 0; i < source->n; i++) {
+        psImage *image = source->data[i]; // The image of interest
+        if (!image) {
+            continue;
+        }
+        numImages++;
+
+        // Only implemented for F32 and U8 images so far.
+        assert(image->type.type == PS_TYPE_F32 || image->type.type == PS_TYPE_U8);
+        // All input types must be the same
+        if (type == 0) {
+            type = image->type.type;
+        }
+        assert(type == image->type.type);
+
+        // Size of cell in x and y
+        int xParity = xFlip->data.U8[i] ? -1 : 1;
+        int yParity = yFlip->data.U8[i] ? -1 : 1;
+        psTrace("psModules.camera", 5, "Extent of cell %d: %d -> %d , %d -> %d\n", i, x0->data.S32[i],
+                x0->data.S32[i] + xParity * xBinSource->data.S32[i] * image->numCols, y0->data.S32[i],
+                y0->data.S32[i] + yParity * yBinSource->data.S32[i] * image->numRows);
+
+        COMPARE(x0->data.S32[i], xMin, xMax);
+        COMPARE(y0->data.S32[i], yMin, yMax);
+        // Subtract the parity to get the inclusive limit (not exclusive)
+        COMPARE(x0->data.S32[i] + xParity * xBinSource->data.S32[i] * image->numCols - xParity, xMin, xMax);
+        COMPARE(y0->data.S32[i] + yParity * yBinSource->data.S32[i] * image->numRows - yParity, yMin, yMax);
+    }
+    if (numImages == 0) {
+        return NULL;
+    }
+
+    // Set up the image
+    // Since both upper and lower values are inclusive, we need to add one to the size
+    float xSize = (float)(xMax - xMin + 1) / (float)xBinTarget;
+    if (xSize - (int)xSize > 0) {
+        xSize += 1;
+    }
+    float ySize = (float)(yMax - yMin + 1) / (float)yBinTarget;
+    if (ySize - (int)ySize > 0) {
+        ySize += 1;
+    }
+
+    psTrace("psModules.camera", 3, "Spliced image will be %dx%d\n", (int)xSize, (int)ySize);
+    psImage *mosaic = psImageAlloc((int)xSize, (int)ySize, type); // The mosaic image
+    psImageInit(mosaic, 0);
+
+    // Next pass through the images to do the mosaicking
+    for (int i = 0; i < source->n; i++) {
+        psImage *image = source->data[i]; // The image of interest
+        if (!image) {
+            continue;
+        }
+        int xParity = xFlip->data.U8[i] ? -1 : 1; // Parity difference, in x
+        int yParity = yFlip->data.U8[i] ? -1 : 1; // Parity difference, in y
+        int xTargetBase = (x0->data.S32[i] - xMin) / xBinTarget; // The base x position in the target frame
+        int yTargetBase = (y0->data.S32[i] - yMin) / yBinTarget; // The base y position in the target frame
+        if (xBinSource->data.S32[i] == xBinTarget && yBinSource->data.S32[i] == yBinTarget &&
+                xFlip->data.U8[i] == 0 && yFlip->data.U8[i] == 0) {
+            // Let someone else do the hard work
+            psImageOverlaySection(mosaic, image, xTargetBase, yTargetBase, "+");
+        } else if (xBinSource->data.S32[i] == xBinTarget && yBinSource->data.S32[i] == yBinTarget) {
+            // There's a difference with the parities, but we don't have to worry about binning
+
+            #define COPY_WITH_PARITY_DIFFERENCE(TYPE) \
+        case PS_TYPE_##TYPE: { \
+                for (int y = 0; y < image->numRows; y++) { \
+                    int yTarget =  yTargetBase + yParity * y; \
+                    for (int x = 0; x < image->numCols; x++) { \
+                        int xTarget = xTargetBase + xParity * x; \
+                        mosaic->data.TYPE[yTarget][xTarget] += image->data.TYPE[y][x]; \
+                    } \
+                } \
+            } \
+            break;
+
+            switch (type) {
+                COPY_WITH_PARITY_DIFFERENCE(F32);
+                COPY_WITH_PARITY_DIFFERENCE(U8);
+            default:
+                psAbort(__func__, "Should never get here.\n");
+            }
+
+        } else {
+            // We have to do all of the hard work ourself
+
+            // In case the original image is binned but the mosaic is not, we need to fill in the
+            // values in the mosaic.
+            #define FILL_IN(TYPE) \
+        case PS_TYPE_##TYPE: \
+            for (int y = 0; y < image->numRows; y++) { \
+                float yTargetBinBase = yTargetBase + yParity * yBinSource->data.S32[i] * y / yBinTarget; \
+                for (int x = 0; x < image->numCols; x++) { \
+                    float xTargetBinBase = xTargetBase + xParity * xBinSource->data.S32[i] * x / xBinTarget; \
+                    for (int j = 0; j < yBinSource->data.S32[i]; j++) { \
+                        int yTarget = (int)(yTargetBinBase + yParity * (float)j / (float)yBinTarget); \
+                        for (int k = 0; k < xBinSource->data.S32[i]; k++) { \
+                            int xTarget = (int)(xTargetBinBase + xParity * (float)k / (float)xBinTarget); \
+                            mosaic->data.TYPE[yTarget][xTarget] += image->data.TYPE[y][x]; \
+                        } \
+                    } \
+                } \
+            } \
+            break;
+
+            switch (type) {
+                FILL_IN(F32);
+                FILL_IN(U8);
+            default:
+                psAbort(__func__, "Should never get here.\n");
+            }
+
+        } // Various difficulty levels
+    } // Iterating over images
+
+    return mosaic;
+}
+
+
+// Add a cell and its various properties to the arrays
+static bool addCell(psArray *images,    // Array of images
+                    psArray *masks,     // Array of masks
+                    psArray *weights,   // Array of weights
+                    psVector *x0,       // Array of X0
+                    psVector *y0,       // Array of Y0
+                    psVector *xBin,     // Array of XBIN
+                    psVector *yBin,     // Array of YBIN
+                    psVector *xFlip,    // Array indicating whether x axis should be flipped
+                    psVector *yFlip,    // Array indicating whether y axis should be flipped
+                    const pmCell *cell, // Cell to add
+                    int *xBinMin,       // The minimum x binning, returned
+                    int *yBinMin,       // The minimum y binning, returned
+                    bool chipStuff,      // Worry about chip stuff as well?
+                    int x0Target, int y0Target, // Target x0 and y0 offsets
+                    int xParityTarget, int yParityTarget // Target parities
+                   )
+{
+    if (!cell) {
+        return false;
+    }
+
+    // Expand the arrays and vectors to handle new data
+    long index = images->n;               // The index to use
+    if (images->n == images->nalloc) {
+        images  = psArrayRealloc(images,  index + CELL_LIST_BUFFER);
+        masks   = psArrayRealloc(masks,   index + CELL_LIST_BUFFER);
+        weights = psArrayRealloc(weights, index + CELL_LIST_BUFFER);
+        x0    = psVectorRealloc(x0,    index+ CELL_LIST_BUFFER);
+        y0    = psVectorRealloc(y0,    index+ CELL_LIST_BUFFER);
+        xBin  = psVectorRealloc(xBin,  index+ CELL_LIST_BUFFER);
+        yBin  = psVectorRealloc(yBin,  index+ CELL_LIST_BUFFER);
+        xFlip = psVectorRealloc(xFlip, index+ CELL_LIST_BUFFER);
+        yFlip = psVectorRealloc(yFlip, index+ CELL_LIST_BUFFER);
+    }
+
+    images->n = index + 1;
+    masks->n = index + 1;
+    weights->n = index + 1;
+    x0->n = index + 1;
+    y0->n = index + 1;
+    xBin->n = index + 1;
+    yBin->n = index + 1;
+    xFlip->n = index + 1;
+    yFlip->n = index + 1;
+
+    bool mdok = true;                   // Status of MD lookup
+    bool good = true;                   // Is everything good?
+
+    // Offset of the cell on the chip
+    int x0Cell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.X0");
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.X0 for cell is not set.\n");
+        good = false;
+    }
+    int y0Cell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.Y0");
+    if (!mdok) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.Y0 for cell is not set.\n");
+        good = false;
+    }
+    psTrace("psModules.camera", 5, "Cell %ld: x0=%d y0=%d\n", index, x0Cell, y0Cell);
+
+    // Offset of the chip on the FPA
+    int x0Chip = 0, y0Chip = 0;
+    if (chipStuff) {
+        pmChip *chip = cell->parent;    // The parent chip
+        if (!chip) {
+            psError(PS_ERR_UNKNOWN, true, "Cell has no parent chip --- can't find CHIP.X0 and CHIP.Y0\n");
+            good = false;
+        }
+        x0Chip = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.X0");
+        if (!mdok) {
+            psError(PS_ERR_UNKNOWN, true, "CHIP.X0 for chip is not set.\n");
+            good = false;
+        }
+        y0Chip = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.Y0");
+        if (!mdok) {
+            psError(PS_ERR_UNKNOWN, true, "CHIP.Y0 for chip is not set.\n");
+            good = false;
+        }
+    }
+
+    // Binning
+    xBin->data.S32[index] = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XBIN");
+    if (!mdok || xBin->data.S32[index] == 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.XBIN for cell is not set.\n");
+        good = false;
+    }
+    if (xBin->data.S32[index] < *xBinMin) {
+        *xBinMin = xBin->data.S32[index];
+    }
+    yBin->data.S32[index] = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YBIN");
+    if (!mdok || yBin->data.S32[index] == 0) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.YBIN for cell is not set.\n");
+        good = false;
+    }
+    if (yBin->data.S32[index] < *yBinMin) {
+        *yBinMin = yBin->data.S32[index];
+    }
+
+    // Do we need to flip?
+    int xParityCell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.XPARITY");
+    if (!mdok || (xParityCell != 1 && xParityCell != -1)) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.XPARITY for cell is not set.\n");
+        good = false;
+    }
+    int yParityCell = psMetadataLookupS32(&mdok, cell->concepts, "CELL.YPARITY");
+    if (!mdok || (yParityCell != 1 && yParityCell != -1)) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.YPARITY for cell is not set.\n");
+        good = false;
+    }
+
+    // Parity of the chip on the FPA
+    int xParityChip = 1, yParityChip = 1;
+    if (chipStuff) {
+        pmChip *chip = cell->parent;    // The parent chip
+        xParityChip = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.XPARITY");
+        if (!mdok || (xParityChip != 1 && xParityChip != -1)) {
+            psError(PS_ERR_UNKNOWN, true, "CHIP.XPARITY for chip is not set.\n");
+            good = false;
+        }
+        yParityChip = psMetadataLookupS32(&mdok, chip->concepts, "CHIP.YPARITY");
+        if (!mdok || (yParityChip != 1 && yParityChip != -1)) {
+            psError(PS_ERR_UNKNOWN, true, "CHIP.YPARITY for chip is not set.\n");
+            good = false;
+        }
+    }
+
+    // Set the flips on the basis of the parity
+    if (xParityCell * xParityChip == xParityTarget) {
+        xFlip->data.U8[index] = 0;
+    } else {
+        xFlip->data.U8[index] = 1;
+    }
+    if (yParityCell * yParityChip == yParityTarget) {
+        yFlip->data.U8[index] = 0;
+    } else {
+        yFlip->data.U8[index] = 1;
+    }
+
+    x0->data.S32[index] = x0Chip + x0Cell - x0Target;
+    y0->data.S32[index] = y0Chip + y0Cell - y0Target;
+
+    // Add the readout to the array of images to be mosaicked
+    psArray *readouts = cell->readouts; // The array of readouts
+    if (readouts->n > 1) {
+        psLogMsg(__func__, PS_LOG_WARN, "Cell contains more than one readout (%ld) --- only the first will "
+                 "be mosaicked.\n", readouts->n);
+    }
+    pmReadout *readout = readouts->data[0]; // The only readout we'll bother with
+
+    // The images to put into the mosaic
+    images->data[index]  = psMemIncrRefCounter(readout->image);
+    weights->data[index] = psMemIncrRefCounter(readout->weight);
+    masks->data[index]   = psMemIncrRefCounter(readout->mask);
+
+    psTrace("psModules.camera", 9, "Added cell (%p) %ld: %d,%d; %d,%d, %d,%d.\n", cell, index,
+            x0->data.S32[index], y0->data.S32[index], xBin->data.S32[index], yBin->data.S32[index],
+            xFlip->data.U8[index], yFlip->data.U8[index]);
+
+    return true;
+}
+
+
+// Mosaic together the cells in a chip
+static bool chipMosaic(psImage **mosaicImage, // The mosaic image, to be returned
+                       psImage **mosaicMask, // The mosaic mask, to be returned
+                       psImage **mosaicWeights, // The mosaic weights, to be returned
+                       int *xBinChip, int *yBinChip, // The binning in x and y, to be returned
+                       const pmChip *chip, // Chip to mosaic
+                       const pmCell *targetCell // Cell to which to mosaic
+                      )
+{
+    assert(mosaicImage);
+    assert(mosaicMask);
+    assert(mosaicWeights);
+    assert(xBinChip);
+    assert(yBinChip);
+    assert(chip);
+    assert(targetCell);
+
+    psArray *images = psArrayAlloc(0); // Array of images that will be mosaicked
+    psArray *weights = psArrayAlloc(0); // Array of weight images to be mosaicked
+    psArray *masks = psArrayAlloc(0); // Array of mask images to be mosaicked
+    psVector *x0 = psVectorAlloc(0, PS_TYPE_S32); // Origin x coordinates
+    psVector *y0 = psVectorAlloc(0, PS_TYPE_S32); // Origin y coordinates
+    psVector *xBin = psVectorAlloc(0, PS_TYPE_S32); // Binning in x
+    psVector *yBin = psVectorAlloc(0, PS_TYPE_S32); // Binning in y
+    psVector *xFlip = psVectorAlloc(0, PS_TYPE_U8); // Flip in x?
+    psVector *yFlip = psVectorAlloc(0, PS_TYPE_U8); // Flip in y?
+
+    // Get the target characteristics
+    bool mdok = true;                   // Status of MD lookup
+    int x0Target = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.X0");
+    if (!mdok) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.X0 is not set for the target cell; assuming 0.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.X0", S32, 0);
+    }
+    int y0Target = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.Y0");
+    if (!mdok) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.Y0 is not set for the target cell; assuming 0.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.Y0", S32, 0);
+    }
+    int xParityTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.XPARITY");
+    if (!mdok || (xParityTarget != -1 && xParityTarget != 1)) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.XPARITY is not set for the target cell; assuming 1.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.XPARITY", S32, 1);
+        xParityTarget = 1;
+    }
+    int yParityTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.YPARITY");
+    if (!mdok || (yParityTarget != -1 && yParityTarget != 1)) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.YPARITY is not set for the target cell; assuming 1.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.YPARITY", S32, 1);
+        yParityTarget = 1;
+    }
+
+    // Binning for the mosaicked chip is the minimum binning allowed by the cells
+    *xBinChip = INT_MAX;
+    *yBinChip = INT_MAX;
+
+    // Set up the required inputs
+    bool allGood = true;                // Is everything good, well-behaved?
+    psArray *cells = chip->cells;       // The array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // The cell of interest
+        if (!cell || !cell->data_exists) {
+            continue;
+        }
+        allGood |= addCell(images, masks, weights, x0, y0, xBin, yBin, xFlip, yFlip,
+                           cell, xBinChip, yBinChip, false, x0Target, y0Target,
+                           xParityTarget, yParityTarget);
+    }
+
+    // Check to see if the target has a smaller binning in mind
+    int xBinTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.XBIN");
+    if (!mdok || xBinTarget == 0) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.XBIN is not set for the target cell; assuming %d.\n",*xBinChip);
+        FIX_CONCEPT(targetCell->concepts, "CELL.XBIN", S32, *xBinChip);
+    } else {
+        *xBinChip = xBinTarget;
+    }
+    int yBinTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.YBIN");
+    if (!mdok || yBinTarget == 0) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.YBIN is not set for the target cell; assuming %d.\n",*yBinChip);
+        FIX_CONCEPT(targetCell->concepts, "CELL.YBIN", S32, *yBinChip);
+    } else {
+        *yBinChip = yBinTarget;
+    }
+
+    // Mosaic the images together and we're done
+    if (allGood) {
+        *mosaicImage = imageMosaic(images, xFlip, yFlip, xBin, yBin, *xBinChip, *yBinChip, x0, y0);
+        *mosaicWeights = imageMosaic(weights, xFlip, yFlip, xBin, yBin, *xBinChip, *yBinChip, x0, y0);
+        *mosaicMask = imageMosaic(masks, xFlip, yFlip, xBin, yBin, *xBinChip, *yBinChip, x0, y0);
+    }
+
+    // Clean up
+    psFree(images);
+    psFree(weights);
+    psFree(masks);
+    psFree(xFlip);
+    psFree(yFlip);
+    psFree(xBin);
+    psFree(yBin);
+    psFree(x0);
+    psFree(y0);
+
+    return allGood;
+}
+
+// Mosaic together the cells in a FPA
+static bool fpaMosaic(psImage **mosaicImage, // The mosaic image, to be returned
+                      psImage **mosaicMask, // The mosaic mask, to be returned
+                      psImage **mosaicWeights, // The mosaic weights, to be returned
+                      int *xBinFPA, int *yBinFPA, // The binning in x and y, to be returned
+                      const pmFPA *fpa,  // FPA to mosaic
+                      const pmChip *targetChip, // Chip to which to mosaic
+                      const pmCell *targetCell // Cell to which to mosaic
+                     )
+{
+    assert(mosaicImage);
+    assert(mosaicMask);
+    assert(mosaicWeights);
+    assert(xBinFPA);
+    assert(yBinFPA);
+    assert(fpa);
+    assert(targetChip);
+    assert(targetCell);
+
+    psArray *images = psArrayAlloc(0); // Array of images that will be mosaicked
+    psArray *weights = psArrayAlloc(0); // Array of weight images to be mosaicked
+    psArray *masks = psArrayAlloc(0); // Array of mask images to be mosaicked
+    psVector *x0 = psVectorAlloc(0, PS_TYPE_S32); // Origin x coordinates
+    psVector *y0 = psVectorAlloc(0, PS_TYPE_S32); // Origin y coordinates
+    psVector *xBin = psVectorAlloc(0, PS_TYPE_S32); // Binning in x
+    psVector *yBin = psVectorAlloc(0, PS_TYPE_S32); // Binning in y
+    psVector *xFlip = psVectorAlloc(0, PS_TYPE_U8); // Flip in x?
+    psVector *yFlip = psVectorAlloc(0, PS_TYPE_U8); // Flip in y?
+
+    // Get the target characteristics
+    bool mdok = true;                   // Status of MD lookup
+    int x0Target = psMetadataLookupS32(&mdok, targetChip->concepts, "CHIP.X0");
+    if (!mdok) {
+        psLogMsg(__func__, PS_LOG_WARN, "CHIP.X0 is not set for the target chip; assuming 0.\n");
+        FIX_CONCEPT(targetChip->concepts, "CHIP.X0", S32, 0);
+    }
+    int y0Target = psMetadataLookupS32(&mdok, targetChip->concepts, "CHIP.Y0");
+    if (!mdok) {
+        psLogMsg(__func__, PS_LOG_WARN, "CHIP.Y0 is not set for the target chip; assuming 0.\n");
+        FIX_CONCEPT(targetChip->concepts, "CHIP.Y0", S32, 0);
+    }
+    x0Target += psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.X0");
+    if (!mdok) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.X0 is not set for the target cell; assuming 0.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.X0", S32, 0);
+    }
+    y0Target += psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.Y0");
+    if (!mdok) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.Y0 is not set for the target cell; assuming 0.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.Y0", S32, 0);
+    }
+    int xParityChipTarget = psMetadataLookupS32(&mdok, targetChip->concepts, "CHIP.XPARITY");
+    if (!mdok || (xParityChipTarget != -1 && xParityChipTarget != 1)) {
+        psLogMsg(__func__, PS_LOG_WARN, "CHIP.XPARITY is not set for the target chip; assuming 1.\n");
+        FIX_CONCEPT(targetChip->concepts, "CHIP.XPARITY", S32, 1);
+        xParityChipTarget = 1;
+    }
+    int yParityChipTarget = psMetadataLookupS32(&mdok, targetChip->concepts, "CHIP.YPARITY");
+    if (!mdok || (yParityChipTarget != -1 && yParityChipTarget != 1)) {
+        psLogMsg(__func__, PS_LOG_WARN, "CHIP.YPARITY is not set for the target chip; assuming 1.\n");
+        FIX_CONCEPT(targetChip->concepts, "CHIP.YPARITY", S32, 1);
+        yParityChipTarget = 1;
+    }
+    int xParityCellTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.XPARITY");
+    if (!mdok || (xParityCellTarget != -1 && xParityCellTarget != 1)) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.XPARITY is not set for the target cell; assuming 1.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.XPARITY", S32, 1);
+        xParityCellTarget = 1;
+    }
+    int yParityCellTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.YPARITY");
+    if (!mdok || (yParityCellTarget != -1 && yParityCellTarget != 1)) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.YPARITY is not set for the target cell; assuming 1.\n");
+        FIX_CONCEPT(targetCell->concepts, "CELL.YPARITY", S32, 1);
+        yParityCellTarget = 1;
+    }
+    int xParityTarget = xParityChipTarget * xParityCellTarget;
+    int yParityTarget = yParityChipTarget * yParityCellTarget;
+
+    // Binning for the mosaicked chip is the minimum binning allowed by the cells
+    *xBinFPA = INT_MAX;
+    *yBinFPA = INT_MAX;
+
+    // Set up the required inputs
+    bool allGood = true;                // Is everything good, well-behaved?
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // The chip of interest
+        if (!chip || !chip->data_exists) {
+            continue;
+        }
+        psArray *cells = chip->cells;   // The array of cells
+        for (int j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[j];  // The cell of interest
+            if (!cell || !cell->data_exists) {
+                continue;
+            }
+            allGood |= addCell(images, masks, weights, x0, y0, xBin, yBin, xFlip, yFlip,
+                               cell, xBinFPA, yBinFPA, true, x0Target, y0Target,
+                               xParityTarget, yParityTarget);
+        }
+    }
+
+    // Check to see if the target has a smaller binning in mind
+    int xBinTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.XBIN");
+    if (mdok && xBinTarget != 0) {
+        *xBinFPA = xBinTarget;
+    }
+    int yBinTarget = psMetadataLookupS32(&mdok, targetCell->concepts, "CELL.YBIN");
+    if (mdok && yBinTarget != 0) {
+        *yBinFPA = yBinTarget;
+    }
+
+    // Mosaic the images together and we're done
+    if (allGood) {
+        *mosaicImage = imageMosaic(images, xFlip, yFlip, xBin, yBin, *xBinFPA, *yBinFPA, x0, y0);
+        *mosaicWeights = imageMosaic(weights, xFlip, yFlip, xBin, yBin, *xBinFPA, *yBinFPA, x0, y0);
+        *mosaicMask = imageMosaic(masks, xFlip, yFlip, xBin, yBin, *xBinFPA, *yBinFPA, x0, y0);
+    }
+
+    // Clean up
+    psFree(images);
+    psFree(weights);
+    psFree(masks);
+    psFree(xFlip);
+    psFree(yFlip);
+    psFree(xBin);
+    psFree(yBin);
+    psFree(x0);
+    psFree(y0);
+
+    return allGood;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Mosaic all the cells in a chip together.
+//
+// It is desirable to do this without using psImageOverlay (or similar) if it can be at all avoided (because
+// it's really really slow in that case).  There are therefore two cases:
+//
+// 1. The HDU is at the Chip or FPA level.  This is the fast case, and only works if the HDU is "nice", by
+// which I mean:
+//
+//    - the CELL.TRIMSECs are contiguous on the HDU image
+//    - the CELL.PARITYs are identically +1
+//    - the CELL.XBIN and CELL.YBIN are all identical
+//
+// Then we can just use psImageSubset to get the "mosaicked" chip.
+//
+//
+// 2. The HDU is at the cell level, or the above requirements are not met, in which case we mosaic the cells.
+// This is the slow case.  We need to:
+//
+//    - Throw away the bias regions
+//    - Convert all cells to common parity
+//    - Mosaic the cells into an HDU image using CELL.X0 and CELL.Y0
+//    - Update CELL.TRIMSECs
+//
+// Once the demands of case 1 have been met, or case 2 has been performed, then we can create a cell to hold
+// the mosaic image.
+bool pmChipMosaic(pmChip *target,       // Target chip --- may contain only a single cell
+                  const pmChip *source  // Source chip whose cells will be mosaicked
+                 )
+{
+    // Target exists, and has only a single cell
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(target->cells, false);
+    if (target->cells->n != 1) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Target chip for mosaicking must contain a single cell.\n");
+        return false;
+    }
+    pmCell *targetCell = target->cells->data[0]; // The target cell
+    PS_ASSERT_PTR_NON_NULL(targetCell, false);
+    // Source exists
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+
+    psImage *mosaicImage   = NULL;      // The mosaic image
+    psImage *mosaicMask    = NULL;      // The mosaic mask
+    psImage *mosaicWeights = NULL;      // The mosaic weights
+
+    // Find the HDU
+    psRegion *chipRegion = NULL;        // Region on the HDU that corresponds to the chip
+    int xBin = 0, yBin = 0;             // Binning for the chip mosaic
+    if ((chipRegion = niceChip(&xBin, &yBin, source))) {
+        // Case 1 --- we need only cut out the region
+        psTrace("psModules.camera", 1, "Case 1 mosaicking: simple cut-out.\n");
+        pmHDU *hdu = source->hdu;       // The HDU that has the pixels
+        if (!hdu || !hdu->images) {
+            hdu = source->parent->hdu;
+        }
+        mosaicImage = psMemIncrRefCounter(psImageSubset(hdu->images->data[0], *chipRegion));
+        if (hdu->masks) {
+            mosaicMask = psMemIncrRefCounter(psImageSubset(hdu->masks->data[0], *chipRegion));
+        }
+        if (hdu->weights) {
+            mosaicWeights = psMemIncrRefCounter(psImageSubset(hdu->weights->data[0], *chipRegion));
+        }
+    } else {
+        // Case 2 --- we need to mosaic by cut and paste
+        psTrace("psModules.camera", 1, "Case 2 mosaicking: cut and paste.\n");
+        if (!chipMosaic(&mosaicImage, &mosaicMask, &mosaicWeights, &xBin, &yBin, source, targetCell)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to mosaic cells.\n");
+            return false;
+        }
+        chipRegion = psRegionAlloc(NAN, NAN, NAN, NAN); // We've cut and paste, so there's no valid trimsec
+    }
+    psTrace("psModules.camera", 1, "xBin,yBin: %d,%d\n", xBin, yBin);
+
+    // Set the concepts for the target cell
+    psList *sourceCells = psArrayToList(source->cells); // List of cells
+    pmConceptsAverageCells(targetCell, sourceCells, chipRegion, NULL);
+    {
+        psMetadataItem *item = psMetadataLookup(targetCell->concepts, "CELL.X0");
+        item->data.S32 = 0;
+        item = psMetadataLookup(targetCell->concepts, "CELL.Y0");
+        item->data.S32 = 0;
+        item = psMetadataLookup(targetCell->concepts, "CELL.XBIN");
+        item->data.S32 = xBin;
+        item = psMetadataLookup(targetCell->concepts, "CELL.YBIN");
+        item->data.S32 = yBin;
+    }
+
+    psFree(sourceCells);
+    psFree(chipRegion);
+
+    // Currently, there's nothing interesting in the chip concepts that needs to be updated.
+
+    // Now make a new readout to go in the target cell
+    pmReadout *newReadout = pmReadoutAlloc(targetCell); // New readout
+    newReadout->image  = mosaicImage;
+    newReadout->mask   = mosaicMask;
+    newReadout->weight = mosaicWeights;
+    psFree(newReadout);                 // Drop reference
+
+    // Data now exists in the targets
+    pmChipSetDataStatus(target, true);
+    pmCellSetDataStatus(targetCell, true);
+    newReadout->data_exists = true;
+
+    // Update the headers
+    pmHDU *sourceHDU = pmHDUFromChip(source); // The HDU for the source
+    pmHDU *targetHDU = pmHDUFromChip(target); // The HDU for the target
+    targetHDU->header = psMetadataCopy(targetHDU->header, sourceHDU->header);
+    pmHDU *phu = pmHDUGetHighest(target->parent, target, NULL);
+    if (!phu || !phu->header) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find HDU after mosaicking.\n");
+        return false;
+    }
+    if (!pmConfigConformHeader(phu->header, phu->format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to conform header after mosaicking.\n");
+        return false;
+    }
+
+    return true;
+}
+
+
+// Same deal with the FPA
+bool pmFPAMosaic(pmFPA *target,         // Target FPA --- may contain only a single chip with a single cell
+                 const pmFPA *source    // FPA whose chips and cells will be mosaicked
+                )
+{
+    // Target exists, and has only a single chip with single cell
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(target->chips, false);
+    if (target->chips->n != 1) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Target FPA for mosaicking must contain a single chip.\n");
+        return false;
+    }
+    pmChip *targetChip = target->chips->data[0]; // The target chip
+    PS_ASSERT_PTR_NON_NULL(targetChip, false);
+    PS_ASSERT_PTR_NON_NULL(targetChip->cells, false);
+    if (target->chips->n != 1) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Target FPA for mosaicking must contain a single cell.\n");
+        return false;
+    }
+    pmCell *targetCell = targetChip->cells->data[0]; // The target cell
+    PS_ASSERT_PTR_NON_NULL(targetCell, false);
+    // Source exists
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+    psImage *mosaicImage   = NULL;      // The mosaic image
+    psImage *mosaicMask    = NULL;      // The mosaic mask
+    psImage *mosaicWeights = NULL;      // The mosaic weights
+
+    // Find the HDU
+    psRegion *fpaRegion = NULL;         // Region on the HDU that corresponds to the FPA
+    int xBin = 0, yBin = 0;             // Binning for the FPA mosaic
+    if ((fpaRegion = niceFPA(&xBin, &yBin, source))) {
+        // Case 1 --- we need only cut out the region
+        psTrace("psModules.camera", 1, "Case 1 mosaicking: simple cut-out.\n");
+        pmHDU *hdu = source->hdu;         // The HDU that has the pixels
+        mosaicImage = psMemIncrRefCounter(psImageSubset(hdu->images->data[0], *fpaRegion));
+        if (hdu->masks) {
+            mosaicMask = psMemIncrRefCounter(psImageSubset(hdu->masks->data[0], *fpaRegion));
+        }
+        if (hdu->weights) {
+            mosaicWeights = psMemIncrRefCounter(psImageSubset(hdu->weights->data[0], *fpaRegion));
+        }
+    } else {
+        // Case 2 --- we need to mosaic by cut and paste
+        psTrace("psModules.camera", 1, "Case 2 mosaicking: cut and paste.\n");
+        if (!fpaMosaic(&mosaicImage, &mosaicMask, &mosaicWeights, &xBin, &yBin, source,
+                       targetChip, targetCell)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to mosaic chips.\n");
+            return false;
+        }
+        fpaRegion = psRegionAlloc(NAN, NAN, NAN, NAN); // We've cut and paste, so there's no valid trimsec
+    }
+
+    // Set the concepts for the target cell, and add the mosaic in
+    // First we need a list of cells
+    psList *sourceCells = psListAlloc(NULL); // List of source cells
+    psArray *chips = source->chips;        // Array of chips
+    pmChip *firstSourceChip = NULL;     // The first chip in the source FPA; for headers
+    pmCell *firstSourceCell = NULL;     // The first cell in the source FPA; for headers
+    for (long i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // Chip of interest
+        if (!chip || !chip->data_exists) {
+            continue;
+        }
+        psArray *cells = chip->cells;
+        for (long j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[j]; // Cell of interest
+            if (!cell || !cell->data_exists) {
+                continue;
+            }
+            psListAdd(sourceCells, PS_LIST_TAIL, cell);
+
+            // These are valid chip and cell to use for the header; grab the first such
+            if (!firstSourceCell && !firstSourceChip) {
+                firstSourceCell = cell;
+                firstSourceChip = chip;
+            }
+        }
+    }
+    pmConceptsAverageCells(targetCell, sourceCells, fpaRegion, NULL);
+    {
+        psMetadataItem *item = psMetadataLookup(targetCell->concepts, "CELL.X0");
+        item->data.S32 = 0;
+        item = psMetadataLookup(targetCell->concepts, "CELL.Y0");
+        item->data.S32 = 0;
+        item = psMetadataLookup(targetCell->concepts, "CELL.XBIN");
+        item->data.S32 = xBin;
+        item = psMetadataLookup(targetCell->concepts, "CELL.YBIN");
+        item->data.S32 = yBin;
+    }
+    psFree(sourceCells);
+    psFree(fpaRegion);
+
+    // Currently, there's nothing interesting in the chip concepts that needs to be updated.
+
+    // Copy the concepts for the target FPA
+    target->concepts = psMetadataCopy(target->concepts, source->concepts);
+
+    // Now make a new readout to go in the new cell
+    pmReadout *newReadout = pmReadoutAlloc(targetCell); // New readout
+    newReadout->image  = mosaicImage;
+    newReadout->mask   = mosaicMask;
+    newReadout->weight = mosaicWeights;
+    psFree(newReadout);                 // Drop reference
+
+    // Data now exists in the targets
+    pmChipSetDataStatus(targetChip, true);
+    pmCellSetDataStatus(targetCell, true);
+    newReadout->data_exists = true;
+
+    // Update the headers
+    pmHDU *sourceHDU = pmHDUGetHighest(source, firstSourceChip, firstSourceCell); // The HDU for the source
+    if (!sourceHDU) {
+        psLogMsg(__func__, PS_LOG_WARN, "Unable to find HDU in source FPA; unable to copy headers.\n");
+        return false;
+    }
+    pmHDU *targetHDU = pmHDUGetHighest(target, targetChip, targetCell); // The HDU for the target
+    if (!targetHDU) {
+        psLogMsg(__func__, PS_LOG_WARN, "Unable to find HDU in target FPA; unable to copy headers.\n");
+        return false;
+    }
+    targetHDU->header = psMetadataCopy(targetHDU->header, sourceHDU->header);
+
+    if (!pmConfigConformHeader(targetHDU->header, targetHDU->format)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to conform header after mosaicking.\n");
+        return false;
+    }
+
+    return true;
+}
+
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAMosaic.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAMosaic.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAMosaic.h	(revision 9533)
@@ -0,0 +1,16 @@
+#ifndef PM_CHIP_MOSAIC_H
+#define PM_CHIP_MOSAIC_H
+
+#include "pmFPA.h"
+
+// Mosaic all cells within a chip
+bool pmChipMosaic(pmChip *target,       // Target chip --- may contain only a single cell
+                  const pmChip *source  // Source chip whose cells will be mosaicked
+                 );
+
+// Mosaic all cells within an FPA
+bool pmFPAMosaic(pmFPA *target,         // Target FPA --- may contain only a single chip with a single cell
+                 const pmFPA *source    // FPA whose chips and cells will be mosaicked
+                );
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPARead.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPARead.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPARead.c	(revision 9533)
@@ -0,0 +1,664 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <strings.h>
+#include <assert.h>
+#include <pslib.h>
+
+#include "pmFPA.h"
+#include "pmHDU.h"
+#include "pmHDUUtils.h"
+#include "pmConcepts.h"
+#include "pmFPAHeader.h"
+
+#include "pmFPARead.h"
+
+#define MAX(x,y) ((x) > (y) ? (x) : (y))
+#define MIN(x,y) ((x) < (y) ? (x) : (y))
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Carve a readout from the image pixels
+static bool readoutCarve(pmReadout *readout, // Readout to be carved up
+                         psImage **target, // Target pointer for the carved image
+                         psImage *image, // Image that will be carved
+                         const psRegion *trimsec, // Trim section
+                         const psList *biassecs // Bias sections
+                        )
+{
+    assert(readout);
+    assert(image);
+    assert(trimsec);
+    assert(biassecs);
+
+    // The image corresponding to the trim region
+    if (psRegionIsNaN(*trimsec)) {
+        psString regionString = psRegionToString(*trimsec);
+        psError(PS_ERR_UNKNOWN, true, "Invalid trim section: %s\n", regionString);
+        psFree(regionString);
+        psFree(readout);
+        return false;
+    }
+    psRegion region = psRegionSet(MAX(trimsec->x0 - readout->col0, 0), // x0
+                                  MIN(trimsec->x1 - readout->col0, image->numCols), // x1
+                                  MAX(trimsec->y0 - readout->row0, 0), // y0
+                                  MIN(trimsec->y1 - readout->row0, image->numRows) // y1
+                                 );
+    if (readout->image) {
+        psFree(readout->image);         // Make way!
+    }
+    *target = psMemIncrRefCounter(psImageSubset(image, region));
+
+    // Get the list of overscans
+    if (readout->bias->n != 0) {
+        // Make way!
+        psFree(readout->bias);
+        readout->bias = psListAlloc(NULL);
+    }
+    psListIterator *iter = psListIteratorAlloc((psList*)biassecs, PS_LIST_HEAD, false); // Iterator
+    psRegion *biassec = NULL;       // A BIASSEC region from the list
+    while ((biassec = psListGetAndIncrement(iter))) {
+        if (psRegionIsNaN(*biassec)) {
+            psString regionString = psRegionToString(*biassec);
+            psError(PS_ERR_IO, true, "Invalid bias section: %s\n", regionString);
+            psFree(regionString);
+            psFree(readout);
+            return false;
+        }
+        psRegion region = psRegionSet(MAX(biassec->x0 - readout->col0, 0), // x0
+                                      MIN(biassec->x1 - readout->col0, image->numCols), // x1
+                                      MAX(biassec->y0 - readout->row0, 0), // y0
+                                      MIN(biassec->y1 - readout->row0, image->numRows) // y1
+                                     );
+        psImage *overscan = psMemIncrRefCounter(psImageSubset(image, region));
+        psListAdd(readout->bias, PS_LIST_TAIL, overscan);
+        psFree(overscan);
+    }
+    psFree(iter);
+
+    return true;
+}
+
+// Read a component of a readout
+psImage *readoutReadComponent(psImage *image, // Image into which to read
+                              psFits *fits, // FITS file from which to read
+                              const psRegion *region, // Region to read
+                              int readdir, // Read direction (1=rows, 2=cols)
+                              int min,  // Minimum row/col number to read
+                              int max,   // Maximum row/col number to read
+                              int z,     // Image plane to read
+                              float bad // Bad value
+                             )
+{
+    assert(fits);
+    assert(region);
+
+    bool resize = false;                // Do we need to resize the image once read?
+    psRegion toRead = psRegionSet(region->x0, region->x1, region->y0, region->y1); // Region to read
+    psRegion fullSize = toRead;         // Full sized region
+    if (readdir == 1) {
+        if (toRead.y0 <= min) {
+            toRead.y0 = min;
+        } else {
+            fullSize.y0 = min;
+            resize = true;
+        }
+        if (toRead.y1 >= max) {
+            toRead.y1 = max;
+        } else {
+            fullSize.y1 = max;
+            resize = false;
+        }
+    } else if (readdir == 2) {
+        if (toRead.x0 <= min) {
+            toRead.x0 = min;
+        } else {
+            fullSize.x0 = min;
+            resize = true;
+        }
+        if (toRead.x1 >= max) {
+            toRead.x1 = max;
+        } else {
+            fullSize.x1 = max;
+            resize = false;
+        }
+    } else {
+        psAbort(__func__, "Read direction can only be 1 (rows) or 2 (cols).\n");
+    }
+
+    psTrace("psModules.camera", 5, "Reading section [%.0f:%.0f,%.0f:%.0f]\n",
+            toRead.x0, toRead.x1, toRead.y0, toRead.y1);
+    image = psFitsReadImageBuffer(image, fits, toRead, z); // Desired pixels
+    psTrace("psModules.camera", 7, "Image is %dx%d\n", image->numCols, image->numRows);
+
+    // XXX: We only support F32 for now
+    if (image->type.type != PS_TYPE_F32) {
+        psImage *temp = psImageCopy(NULL, image, PS_TYPE_F32);
+        psFree(image);
+        image = temp;
+    }
+
+    if (resize) {
+        // For some reason, the region of interest is smaller than the number of pixels we want.
+        psTrace("psModules.camera", 5, "Resizing image to %.0fx%.0f\n",
+                fullSize.x1 - fullSize.x0, fullSize.y1 - fullSize.y0);
+        psImage *temp = psImageAlloc(fullSize.x1 - fullSize.x0, fullSize.y1 - fullSize.y0, image->type.type);
+        psImageInit(temp, bad);
+        psImageOverlaySection(temp, image, toRead.x0, toRead.y0, "=");
+        psFree(image);
+        image = temp;
+    }
+
+    return image;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Read the next readout; return true if we read pixels in.
+//
+// Note that this doesn't put pixels in the HDU.  It is therefore NOT COMPATIBLE with pmCellWrite,
+// pmChipWrite, and pmFPAWrite (or any function that uses or creates an HDU for that matter).
+// Use pmReadoutWriteNext to write the data that's read by this function.
+bool pmReadoutReadNext(pmReadout *readout, // Readout into which to read
+                       psFits *fits,    // FITS file from which to read
+                       int z,           // Readout number/plane; zero-offset indexing
+                       int numScans     // The number of scans to read
+                      )
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_INT_NONNEGATIVE(z, false);
+    PS_ASSERT_INT_NONNEGATIVE(numScans, false);
+
+    // Get the HDU and read the header
+    pmCell *cell = readout->parent;     // The parent cell
+
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU
+    if (!hdu || hdu->blankPHU) {
+        return false;
+    }
+
+    if (!pmCellReadHeader(cell, fits)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell!\n");
+        return false;
+    }
+
+    // Make sure we have the information we need
+    if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CAMERA |
+                            PM_CONCEPT_SOURCE_DEFAULTS, true, NULL)) {
+        psError(PS_ERR_IO, false, "Failed to read concepts for cell.\n");
+        return false;
+    }
+
+    // Get the trim and bias sections
+    bool mdok = true;                   // Status of MD lookup
+    psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim sections
+    if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+        psError(PS_ERR_IO, true, "CELL.TRIMSEC is not set.\n");
+        return false;
+    }
+    psList *biassecs = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.BIASSEC"); // Bias sections
+    if (!mdok || !biassecs) {
+        psError(PS_ERR_IO, true, "CELL.BIASSEC is not set.\n");
+        return false;
+    }
+    int readdir = psMetadataLookupS32(&mdok, cell->concepts, "CELL.READDIR"); // Read direction
+    if (!mdok || readdir == 0 || (readdir != 1 && readdir != 2)) {
+        psError(PS_ERR_IO, true, "CELL.READDIR is not set to -1 or +1.\n");
+        return false;
+    }
+    float bad = psMetadataLookupF32(&mdok, cell->concepts, "CELL.BAD"); // Bad level
+    if (!mdok) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.BAD is not set --- assuming zero.\n");
+        bad = 0.0;
+    }
+
+    // Check the third dimension
+    int naxis = psMetadataLookupS32(&mdok, hdu->header, "NAXIS"); // The number of axes
+    if (!mdok) {
+        psError(PS_ERR_IO, true, "Unable to find NAXIS in header for extension %s\n", hdu->extname);
+        return false;
+    }
+    if (naxis == 0) {
+        // No pixels to read, as for a PHU.
+        return false;
+    }
+    if (naxis < 2 || naxis > 3) {
+        psError(PS_ERR_IO, true, "NAXIS in header of extension %s (= %d) is not valid.\n",
+                hdu->extname, naxis);
+        return false;
+    }
+    int naxis3 = 1;                     // The number of image planes
+    if (naxis == 3) {
+        naxis3 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS3");
+        if (!mdok) {
+            psError(PS_ERR_IO, true, "Unable to find NAXIS3 in header for extension %s\n", hdu->extname);
+            return false;
+        }
+    }
+    if (z >= naxis3) {
+        // Nothing to see here.  Move along.
+        return false;
+    }
+
+    // Get the size of the image plane
+    int naxis1 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS1"); // The number of columns
+    if (!mdok) {
+        psError(PS_ERR_IO, true, "Unable to find NAXIS1 in header for extension %s\n", hdu->extname);
+        return false;
+    }
+    int naxis2 = psMetadataLookupS32(&mdok, hdu->header, "NAXIS2"); // The number of columns
+    if (!mdok) {
+        psError(PS_ERR_IO, true, "Unable to find NAXIS2 in header for extension %s\n", hdu->extname);
+        return false;
+    }
+
+    int maxSize;                        // Number of cols,rows in image
+    if (readdir == 1) {
+        maxSize = PS_MIN(naxis2, trimsec->y1);
+    } else {
+        maxSize = PS_MIN(naxis1, trimsec->x1);
+    }
+
+    // Read the FITS image
+    int offset = 0;                     // The offset from the start of the image to where we are now
+    if (readout->image) {
+        if (readdir == 1) {
+            // Reading rows
+            readout->row0 += readout->image->numRows;
+            offset = readout->row0;
+        } else {
+            // Reading columns
+            readout->col0 += readout->image->numCols;
+            offset = readout->col0;
+        }
+    }
+
+    // Blow away existing data.
+    // Do this before returning, so that we're not returning data from a previous read
+    #if 1
+    psFree(readout->image);
+    readout->image = NULL;
+    #endif
+
+    while (readout->bias->n > 0) {
+        psListRemove(readout->bias, PS_LIST_HEAD);
+    }
+
+    if (offset >= maxSize) {
+        // We've read everything there is
+        psTrace("psModules.camera", 7, "Read everything.\n");
+        psFree(readout->image);
+        return false;
+    }
+    int upper = PS_MIN(offset + numScans, maxSize); // The upper limit for the pixel read
+    psTrace("psModules.camera", 7, "offset=%d, upper = %d, image is %dx%d, trimsec [%.0f:%.0f,%.0f:%.0f]\n",
+            offset, upper, naxis1, naxis2, trimsec->x0, trimsec->x1, trimsec->y0, trimsec->y1);
+
+    // Get the new the trim section
+    readout->image = readoutReadComponent(readout->image, fits, trimsec, readdir,
+                                          offset, upper, z, bad); // The image
+
+    // Get the new bias sections
+    psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator for BIASSEC
+    psRegion *biassec = NULL;           // Bias section from iteration
+    while ((biassec = psListGetAndIncrement(biassecsIter))) {
+        psImage *bias = readoutReadComponent(NULL, fits, biassec, readdir, offset, upper, z, bad); // The bias
+        psListAdd(readout->bias, PS_LIST_TAIL, bias);
+        psFree(bias);                   // Drop reference
+    }
+    psFree(biassecsIter);
+
+    return true;
+}
+
+
+// Read in the cell, and allocate the readouts
+bool pmCellRead(pmCell *cell,           // Cell to read into
+                psFits *fits,           // FITS file from which to read
+                psDB *db                // Database handle, for "concepts" ingest
+               )
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU
+    if (!hdu) {
+        return true;                    // We read everything we could
+    }
+    if (!hdu->images && !pmHDURead(hdu, fits)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read HDU for cell.\n");
+        return false;
+    }
+
+    if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+        //psError(PS_ERR_UNKNOWN, false, "Failed to read concepts for cell");
+        //return false;
+        psWarning("Difficulty reading concepts for cell; attempting to proceed.");
+    }
+
+    // Having read the cell, we now have to cut it up
+    psRegion *trimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC");
+    psList *biassecs = psMetadataLookupPtr(NULL, cell->concepts, "CELL.BIASSEC");
+    if (psRegionIsNaN(*trimsec)) {
+        psError(PS_ERR_IO, false, "CELL.TRIMSEC is not set --- can't read cell.\n");
+        return false;
+    }
+
+    // Iterate over each of the image planes
+    for (int i = 0; i < hdu->images->n; i++) {
+        psImage *image = hdu->images->data[i]; // The i-th plane
+
+        // XXX: Type conversion here to support the modules, which don't have multiple type support yet
+        if (image->type.type != PS_TYPE_F32) {
+            psImage *temp = psImageCopy(NULL, image, PS_TYPE_F32); // Temporary image
+            psFree(hdu->images->data[i]);
+            hdu->images->data[i] = temp;
+            image = temp;
+        }
+
+        pmReadout *readout;             // Readout into which to read
+        if (cell->readouts->n > i && cell->readouts->data[i]) {
+            readout = psMemIncrRefCounter(cell->readouts->data[i]);
+        } else {
+            readout = pmReadoutAlloc(cell);
+        }
+        if (!readoutCarve(readout, &readout->image, image, trimsec, biassecs)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false,
+                    "Unable to carve readout into image and bias sections for %d the plane.", i);
+            return NULL;
+        }
+        psFree(readout);                // Drop reference
+    }
+
+    pmCellSetDataStatus(cell, true);
+    return true;
+}
+
+// Read in the component cells
+bool pmChipRead(pmChip *chip,           // Chip to read into
+                psFits *fits,           // FITS file from which to read
+                psDB *db                // Database handle, for "concepts" ingest
+               )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    bool success = false;               // Were we able to read at least one HDU?
+    psArray *cells = chip->cells;       // Array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // The cell of interest
+        success |= pmCellRead(cell, fits, db);
+    }
+    if (success) {
+        if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER, true, true, NULL)) {
+            psError(PS_ERR_IO, false, "Failed to read concepts for FPA.\n");
+            return false;
+        }
+        // XXX probably could just use chip->data_exists
+        pmChipSetDataStatus(chip, true);
+    }
+
+    return success;
+}
+
+// Read in the component chips
+bool pmFPARead(pmFPA *fpa,              // FPA to read into
+               psFits *fits,            // FITS file from which to read
+               psDB *db                 // Database handle, for "concepts" ingest
+              )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    bool success = false;               // Were we able to read at least one HDU?
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // The cell of interest
+        success |= pmChipRead(chip, fits, db);
+    }
+    if (success) {
+        if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+            psError(PS_ERR_IO, false, "Failed to read concepts for FPA.\n");
+            return false;
+        }
+    } else {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read any chips in FPA");
+    }
+
+    return success;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading the mask
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Read the mask into the cell, and allocate the readouts
+bool pmCellReadMask(pmCell *cell,           // Cell to read into
+                    psFits *fits,           // FITS file from which to read
+                    psDB *db                // Database handle, for "concepts" ingest
+                   )
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU
+    if (!hdu) {
+        return false;                    // Nothing to see here; move along
+    }
+    if (!hdu->images && !pmHDURead(hdu, fits)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read HDU for cell.\n");
+        return false;
+    }
+
+    if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Failed to read concepts for cell");
+        return false;
+    }
+
+    // Having read the cell, we now have to cut it up
+    psRegion *trimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC");
+
+    // Iterate over each of the image planes
+    for (int i = 0; i < hdu->images->n; i++) {
+        psImage *image = hdu->images->data[i]; // The i-th plane
+
+        if (image->type.type != PS_TYPE_U8) {
+            psImage *temp = psImageCopy(NULL, image, PS_TYPE_U8); // Temporary image
+            psFree(hdu->images->data[i]);
+            hdu->images->data[i] = temp;
+            image = temp;
+        }
+
+        pmReadout *readout;             // Readout into which to read
+        if (cell->readouts->n > i && cell->readouts->data[i]) {
+            readout = psMemIncrRefCounter(cell->readouts->data[i]);
+        } else {
+            readout = pmReadoutAlloc(cell);
+        }
+        if (!readoutCarve(readout, &readout->mask, image, trimsec, NULL)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false,
+                    "Unable to carve readout into image and bias sections for %d the plane.", i);
+            return NULL;
+        }
+        psFree(readout);                // Drop reference
+    }
+
+    pmCellSetDataStatus(cell, true);
+    return true;
+}
+
+// Read the mask into the component cells
+bool pmChipReadMask(pmChip *chip,           // Chip to read into
+                    psFits *fits,           // FITS file from which to read
+                    psDB *db                // Database handle, for "concepts" ingest
+                   )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    bool success = false;               // Were we able to read at least one HDU?
+    psArray *cells = chip->cells;       // Array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // The cell of interest
+        success |= pmCellReadMask(cell, fits, db);
+    }
+    if (success) {
+        if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER, true, true, NULL)) {
+            psError(PS_ERR_IO, false, "Failed to read concepts for FPA.\n");
+            return false;
+        }
+        // XXX probably could just use chip->data_exists
+        pmChipSetDataStatus(chip, true);
+    }
+
+    return success;
+}
+
+// Read the mask into the component chips
+bool pmFPAReadMask(pmFPA *fpa,              // FPA to read into
+                   psFits *fits,            // FITS file from which to read
+                   psDB *db                 // Database handle, for "concepts" ingest
+                  )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    bool success = false;               // Were we able to read at least one HDU?
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // The cell of interest
+        success |= pmChipReadMask(chip, fits, db);
+    }
+    if (success) {
+        if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+            psError(PS_ERR_IO, false, "Failed to read concepts for FPA.\n");
+            return false;
+        }
+    } else {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read any chips in FPA");
+    }
+
+    return success;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Reading the weight map
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Read the weight map into the cell, and allocate the readouts
+bool pmCellReadWeight(pmCell *cell,           // Cell to read into
+                      psFits *fits,           // FITS file from which to read
+                      psDB *db                // Database handle, for "concepts" ingest
+                     )
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU
+    if (!hdu) {
+        return false;                    // Nothing to see here; move along
+    }
+    if (!hdu->images && !pmHDURead(hdu, fits)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read HDU for cell.\n");
+        return false;
+    }
+
+    if (!pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+        psError(PS_ERR_UNKNOWN, false, "Failed to read concepts for cell");
+        return false;
+    }
+
+    // Having read the cell, we now have to cut it up
+    psRegion *trimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC");
+
+    // Iterate over each of the image planes
+    for (int i = 0; i < hdu->images->n; i++) {
+        psImage *image = hdu->images->data[i]; // The i-th plane
+
+        if (image->type.type != PS_TYPE_F32) {
+            psImage *temp = psImageCopy(NULL, image, PS_TYPE_F32); // Temporary image
+            psFree(hdu->images->data[i]);
+            hdu->images->data[i] = temp;
+            image = temp;
+        }
+
+        pmReadout *readout;             // Readout into which to read
+        if (cell->readouts->n > i && cell->readouts->data[i]) {
+            readout = psMemIncrRefCounter(cell->readouts->data[i]);
+        } else {
+            readout = pmReadoutAlloc(cell);
+        }
+        if (!readoutCarve(readout, &readout->weight, image, trimsec, NULL)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false,
+                    "Unable to carve readout into image and bias sections for %d the plane.", i);
+            return NULL;
+        }
+        psFree(readout);                // Drop reference
+    }
+
+    pmCellSetDataStatus(cell, true);
+    return true;
+}
+
+// Read the weight map into the component cells
+bool pmChipReadWeight(pmChip *chip,           // Chip to read into
+                      psFits *fits,           // FITS file from which to read
+                      psDB *db                // Database handle, for "concepts" ingest
+                     )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    bool success = false;               // Were we able to read at least one HDU?
+    psArray *cells = chip->cells;       // Array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // The cell of interest
+        success |= pmCellReadWeight(cell, fits, db);
+    }
+    if (success) {
+        if (!pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER, true, true, NULL)) {
+            psError(PS_ERR_IO, false, "Failed to read concepts for FPA.\n");
+            return false;
+        }
+        // XXX probably could just use chip->data_exists
+        pmChipSetDataStatus(chip, true);
+    }
+
+    return success;
+}
+
+// Read the weight map into the component chips
+bool pmFPAReadWeight(pmFPA *fpa,              // FPA to read into
+                     psFits *fits,            // FITS file from which to read
+                     psDB *db                 // Database handle, for "concepts" ingest
+                    )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    bool success = false;               // Were we able to read at least one HDU?
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // The cell of interest
+        success |= pmChipReadWeight(chip, fits, db);
+    }
+    if (success) {
+        if (!pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER, true, NULL)) {
+            psError(PS_ERR_IO, false, "Failed to read concepts for FPA.\n");
+            return false;
+        }
+    } else {
+        psError(PS_ERR_UNKNOWN, false, "Unable to read any chips in FPA");
+    }
+
+    return success;
+}
+
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPARead.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPARead.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPARead.h	(revision 9533)
@@ -0,0 +1,96 @@
+#ifndef PM_FPA_READ_H
+#define PM_FPA_READ_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+bool pmReadoutReadNext(pmReadout *readout, // Readout into which to read
+                       psFits *fits,    // FITS file from which to read
+                       int z,           // Readout number/plane; zero-offset indexing
+                       int numRows      // The number of rows to read
+                      );
+
+bool pmCellRead(pmCell *cell,           // Cell to read into
+                psFits *fits,           // FITS file from which to read
+                psDB *db                // Database handle, for "concepts" ingest
+               );
+
+bool pmChipRead(pmChip *chip,           // Chip to read into
+                psFits *fits,           // FITS file from which to read
+                psDB *db                // Database handle, for "concepts" ingest
+               );
+
+bool pmFPARead(pmFPA *fpa,              // FPA to read into
+               psFits *fits,            // FITS file from which to read
+               psDB *db                 // Database handle, for "concepts" ingest
+              );
+
+bool pmCellReadMask(pmCell *cell,           // Cell to read into
+                    psFits *fits,           // FITS file from which to read
+                    psDB *db                // Database handle, for "concepts" ingest
+                   );
+
+bool pmChipReadMask(pmChip *chip,           // Chip to read into
+                    psFits *fits,           // FITS file from which to read
+                    psDB *db                // Database handle, for "concepts" ingest
+                   );
+
+bool pmFPAReadMask(pmFPA *fpa,              // FPA to read into
+                   psFits *fits,            // FITS file from which to read
+                   psDB *db                 // Database handle, for "concepts" ingest
+                  );
+
+bool pmCellReadWeight(pmCell *cell,           // Cell to read into
+                      psFits *fits,           // FITS file from which to read
+                      psDB *db                // Database handle, for "concepts" ingest
+                     );
+
+bool pmChipReadWeight(pmChip *chip,           // Chip to read into
+                      psFits *fits,           // FITS file from which to read
+                      psDB *db                // Database handle, for "concepts" ingest
+                     );
+
+bool pmFPAReadWeight(pmFPA *fpa,              // FPA to read into
+                     psFits *fits,            // FITS file from which to read
+                     psDB *db                 // Database handle, for "concepts" ingest
+                    );
+
+bool pmCellReadPHU(pmCell *cell,        // Cell to read into
+                   psFits *fits         // FITS file from which to read
+                  );
+
+bool pmChipReadPHU(pmChip *chip,        // Chip to read into
+                   psFits *fits         // FITS file from which to read
+                  );
+
+bool pmFPAReadPHU(pmFPA *fpa,           // FPA to read into
+                  psFits *fits          // FITS file from which to read
+                 );
+
+
+#if 0
+bool pmFPARead(pmFPA *fpa,              // FPA to read into
+               psFits *fits,            // FITS file from which to read
+               psMetadata *phu,         // Primary header
+               psDB *db                 // Database handle, for concept ingest
+              );
+
+psString p_pmFPATranslateName(const psString name, // The name to translate
+                              const pmCell *cell // The cell for which to translate
+                             );
+
+psString p_pmFPATranslateFileExt(psString *extName, // Extension name, to be returned
+                                 const psString filenameExt, // The string to parse into filename and ext
+                                 const pmCell *cell // The cell
+                                );
+
+bool pmFPAReadMask(pmFPA *fpa,          // FPA to read into
+                   psFits *source       // Source FITS file (for the original data)
+                  );
+
+bool pmFPAReadWeight(pmFPA *fpa,        // FPA to read into
+                     psFits *source     // Source FITS file (for the original data)
+                    );
+#endif
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAUtils.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAUtils.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAUtils.c	(revision 9533)
@@ -0,0 +1,59 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include <pslib.h>
+#include "pmFPA.h"
+#include "pmFPAUtils.h"
+
+// Find a chip by name; return the index
+int pmFPAFindChip(const pmFPA *fpa, // FPA in which to find the chip
+                  const char *name // Name of the chip
+                 )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, -1);
+    PS_ASSERT_PTR_NON_NULL(name, -1);
+    if (strlen(name) == 0) {
+        return -1;
+    }
+
+    psArray *chips = fpa->chips;        // Array of chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i]; // The chip of interest
+        psString testName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME"); // Name of this chip
+        if (strcmp(name, testName) == 0) {
+            return i;
+        }
+    }
+
+    psError(PS_ERR_IO, true, "Unable to find chip %s\n", name);
+    return -1;
+}
+
+// Find a cell by name; return the index
+int pmChipFindCell(const pmChip *chip, // Chip in which to find the cell
+                   const char *name // Name of the cell
+                  )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, -1);
+    PS_ASSERT_PTR_NON_NULL(name, -1);
+    if (strlen(name) == 0) {
+        return -1;
+    }
+
+    psArray *cells = chip->cells;    // Array of cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i]; // The cell of interest
+        psString testName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME"); // Name of this cell
+        if (strcmp(name, testName) == 0) {
+            return i;
+        }
+    }
+
+    psError(PS_ERR_IO, true, "Unable to find cell %s\n", name);
+    return -1;
+}
+
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAUtils.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAUtils.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAUtils.h	(revision 9533)
@@ -0,0 +1,17 @@
+#ifndef PM_FPA_UTILS_H
+#define PM_FPA_UTILS_H
+
+#include "pmFPA.h"
+
+// Find a chip by name; return the index
+int pmFPAFindChip(const pmFPA *fpa, // FPA in which to find the chip
+                  const char *name // Name of the chip
+                 );
+
+// Find a cell by name; return the index
+int pmChipFindCell(const pmChip *chip, // Chip in which to find the cell
+                   const char *name // Name of the cell
+                  );
+
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAWrite.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAWrite.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAWrite.c	(revision 9533)
@@ -0,0 +1,253 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <strings.h>
+#include <pslib.h>
+
+#include "pmFPA.h"
+#include "pmHDU.h"
+#include "pmHDUUtils.h"
+#include "pmHDUGenerate.h"
+#include "pmConcepts.h"
+
+#include "pmFPAWrite.h"
+
+bool pmReadoutWriteNext(pmReadout *readout, // Readout to write
+                        psFits *fits,   // FITS file to which to write
+                        int z           // Image plane to write
+                       )
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    pmHDU *hdu = pmHDUFromReadout(readout); // The HDU to which to write
+    if (!hdu) {
+        psError(PS_ERR_IO, false, "Unable to find HDU for readout.\n");
+        return false;
+    }
+    psMetadata *header = hdu->header;   // The FITS header
+    if (!header) {
+        psError(PS_ERR_IO, true, "No FITS header available in the HDU.\n");
+        return false;
+    }
+
+    // We have to rely to a great extent on the FITS header, because otherwise we simply don't know how many
+    // image planes there are (NAXIS3) or the size of the original image (NAXIS1, NAXIS2).
+    bool mdok = true;                   // Status of MD lookup
+    int naxis1 = psMetadataLookupS32(&mdok, header, "NAXIS1"); // Number of columns
+    if (!mdok || naxis1 <= 0) {
+        psError(PS_ERR_IO, true, "Can't find NAXIS1 in header.\n");
+        return false;
+    }
+    int naxis2 = psMetadataLookupS32(&mdok, header, "NAXIS2"); // Number of rows
+    if (!mdok || naxis2 <= 0) {
+        psError(PS_ERR_IO, true, "Can't find NAXIS2 in header.\n");
+        return false;
+    }
+    int naxis3 = psMetadataLookupS32(&mdok, header, "NAXIS3"); // Number of image planes
+    if (!mdok || naxis3 <= 0) {
+        naxis3 = 1;
+    }
+    if (z >= naxis3) {
+        psError(PS_ERR_IO, true, "Specified a plane number (%d) greater than NAXIS3 allows.\n", z);
+        return false;
+    }
+
+    if (!hdu->images) {
+        psError(PS_ERR_IO, true, "No images allocated in HDU.\n");
+        return false;
+    }
+    psImage *image = readout->image; // The image from the HDU to write
+    if (readout->row0 == 0 && readout->col0 == 0 && z == 0) {
+        // Then we can assume that nothing has been written to the FITS file for now
+        if (naxis1 == image->numCols && naxis2 == image->numRows) {
+            // We can write the whole lot at once
+            return psFitsWriteImage(fits, header, image, z, hdu->extname);
+        }
+        // Create a dummy image so we can write something larger than we actually have
+        psImage *dummy = psImageAlloc(naxis1, naxis2, image->type.type); // Dummy image
+        psImageInit(dummy, 0);
+        psImageOverlaySection(dummy, image, 0, 0, "=");
+        bool result = psFitsWriteImage(fits, header, dummy, z, hdu->extname);
+        psFree(dummy);
+        return result;
+    }
+
+    // We can simply update an existing HDU
+    if (hdu->blankPHU && !psFitsMoveExtNum(fits, 0, false)) {
+        psError(PS_ERR_IO, false, "Unable to move to PHU\n");
+        return false;
+    }
+    return psFitsUpdateImage(fits, image, readout->col0, readout->row0, z);
+}
+
+
+
+
+bool pmCellWrite(pmCell *cell,          // Cell to write
+                 psFits *fits,          // FITS file to which to write
+                 psDB *db,              // Database handle for "concepts" update
+                 bool blank             // Write a blank PHU?
+                )
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    psTrace ("pmFPAWrite", 5, "writing to Cell (%d)\n", blank);
+
+    pmHDU *hdu = cell->hdu;             // The HDU
+    if (!hdu) {
+        return true;                    // We wrote every HDU that exists
+    }
+
+    // Generate the HDU if needed --- this is required after a pmFPACopy, or similar, which does not
+    // generate the HDU, but only copies the structure.
+    if (!hdu->blankPHU && !hdu->images) {
+        if (!pmHDUGenerateForCell(cell) || !hdu->images) {
+            psAbort(__func__, "Unable to generate HDU for cell --- likely programming error.\n");
+        }
+    }
+
+    // We only write out a blank PHU if it's specifically requested.
+    bool writeBlank = blank && hdu->blankPHU && !hdu->images; // Write a blank PHU?
+    bool writeImage = !blank && !hdu->blankPHU && hdu->images; // Write an image?
+
+    if (writeBlank || writeImage) {
+        pmConceptSource source = PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CAMERA |
+                                 PM_CONCEPT_SOURCE_DEFAULTS;
+        if (!pmConceptsWriteCell(cell, source, false, NULL)) {
+            psError(PS_ERR_IO, false, "Unable to write concepts for cell.\n");
+            return false;
+        }
+        if (!pmHDUWrite(hdu, fits)) {
+            psError(PS_ERR_IO, false, "Unable to write HDU for cell.\n");
+            return false;
+        }
+    }
+    // No lower levels to which to recurse
+
+    return true;
+}
+
+
+bool pmChipWrite(pmChip *chip,          // Chip to write
+                 psFits *fits,          // FITS file to which to write
+                 psDB *db,              // Database handle for "concepts" update
+                 bool blank,            // Write a blank PHU?
+                 bool recurse           // Recurse to lower levels?
+                )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    pmHDU *hdu = chip->hdu;             // The HDU
+
+    psTrace ("pmFPAWrite", 5, "writing to Chip (%d, %d)\n", blank, recurse);
+
+    // If we have data at this level, try to write it out
+    if (hdu) {
+        // Generate the HDU if needed --- this is required after a pmFPACopy, or similar, which does not
+        // generate the HDU, but only copies the structure.
+        if (!blank && !hdu->blankPHU && !hdu->images) {
+            if (!pmHDUGenerateForChip(chip) || !hdu->images) {
+                psAbort (__func__, "Unable to generate HDU for chip --- likely programming error.\n");
+            }
+        }
+
+        // We only write out a blank PHU if it's specifically requested.
+        bool writeBlank = blank && hdu->blankPHU && !hdu->images; // Write a blank HDU?
+        bool writeImage = !blank && !hdu->blankPHU && hdu->images; // Write an image?
+
+        if (writeBlank || writeImage) {
+            pmConceptSource source = PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CAMERA |
+                                     PM_CONCEPT_SOURCE_DEFAULTS;
+            if (!pmConceptsWriteChip(chip, source, false, true, NULL)) {
+                psError(PS_ERR_IO, false, "Unable to write concepts for chip.\n");
+                return false;
+            }
+            if (!pmHDUWrite(hdu, fits)) {
+                psError(PS_ERR_IO, false, "Unable to write HDU for chip.\n");
+                return false;
+            }
+        }
+    }
+
+    // Recurse to lower level if specifically requested.
+    // XXX recursion implies blank == false (must be called on correct level?)
+    if (recurse) {
+        psArray *cells = chip->cells;       // Array of cells
+        for (int i = 0; i < cells->n; i++) {
+            pmCell *cell = cells->data[i];  // The cell of interest
+            if (!pmCellWrite(cell, fits, db, false)) {
+                psError(PS_ERR_IO, false, "Unable to write Chip.\n");
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+
+
+bool pmFPAWrite(pmFPA *fpa,             // FPA to write
+                psFits *fits,           // FITS file to which to write
+                psDB *db,               // Database handle for "concepts" update
+                bool blank,             // Write a blank PHU?
+                bool recurse            // Recurse to lower levels?
+               )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    pmHDU *hdu = fpa->hdu;              // The HDU
+
+    psTrace ("pmFPAWrite", 5, "writing to FPA (%d, %d)\n", blank, recurse);
+
+    // If we have data at this level, try to write it out
+    if (hdu) {
+        // Generate the HDU if needed --- this is required after a pmFPACopy, or similar, which does not
+        // generate the HDU, but only copies the structure.
+        if (!blank && !hdu->blankPHU && !hdu->images) {
+            if (!pmHDUGenerateForFPA(fpa)) {
+                psAbort("pmFPAWrite", "error generating HDU");
+            }
+            if (!hdu->images) {
+                psAbort("pmFPAWrite", "programming error: failure generating HDU");
+            }
+        }
+
+        // We only write out a blank PHU if it's specifically requested.
+        bool writeBlank = blank && hdu->blankPHU && !hdu->images; // Write a blank PHU?
+        bool writeImage = !blank && !hdu->blankPHU && hdu->images; // Write an image?
+
+        if (writeBlank || writeImage) {
+            pmConceptSource source = PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CAMERA |
+                                     PM_CONCEPT_SOURCE_DEFAULTS;
+            if (!pmConceptsWriteFPA(fpa, source, true, NULL)) {
+                psError(PS_ERR_IO, false, "Unable to write concepts for FPA.\n");
+                return false;
+            }
+            if (!pmHDUWrite(hdu, fits))  {
+                psError(PS_ERR_IO, false, "Unable to write HDU for FPA.\n");
+                return false;
+            }
+        }
+    }
+
+    // Recurse to lower levels if requested
+    if (recurse) {
+        psArray *chips = fpa->chips;        // Array of chips
+        for (int i = 0; i < chips->n; i++) {
+            pmChip *chip = chips->data[i];  // The chip of interest
+            if (!pmChipWrite(chip, fits, db, false, true)) {
+                psError(PS_ERR_IO, false, "Unable to write FPA.\n");
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAWrite.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAWrite.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAWrite.h	(revision 9533)
@@ -0,0 +1,30 @@
+#ifndef PM_FPA_WRITE_H
+#define PM_FPA_WRITE_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+bool pmReadoutWriteNext(pmReadout *readout, // Readout to write
+                        psFits *fits,   // FITS file to which to write
+                        int z           // Image plane to write
+                       );
+
+bool pmCellWrite(pmCell *cell,          // Cell to write
+                 psFits *fits,          // FITS file to which to write
+                 psDB *db,              // Database handle for "concepts" update
+                 bool pixels            // Write the pixels, or only the PHU header?
+                );
+bool pmChipWrite(pmChip *chip,          // Chip to write
+                 psFits *fits,          // FITS file to which to write
+                 psDB *db,              // Database handle for "concepts" update
+                 bool pixels,           // Write the pixels, or only the PHU header?
+                 bool recurse           // Recurse to lower levels?
+                );
+bool pmFPAWrite(pmFPA *fpa,             // FPA to write
+                psFits *fits,           // FITS file to which to write
+                psDB *db,               // Database handle for "concepts" update
+                bool pixels,            // Write the pixels, or only the PHU header?
+                bool recurse            // Recurse to lower levels?
+               );
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPA_JPEG.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPA_JPEG.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPA_JPEG.c	(revision 9533)
@@ -0,0 +1,188 @@
+/** @file  pmFPA_JPEG.c
+ *
+ * This file contains functions to write JPEG images.
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-25 20:23:10 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/*****************************************************************************/
+/* INCLUDE FILES                                                             */
+/*****************************************************************************/
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAfile.h"
+#include "pmFPAview.h"
+#include "pmFPA_JPEG.h"
+
+
+bool pmFPAviewWriteJPEG(const pmFPAview *view, pmFPAfile *file)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        pmFPAWriteJPEG (fpa, view, file);
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        pmChipWriteJPEG (chip, view, file);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        pmCellWriteJPEG (cell, view, file);
+        return true;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    pmReadoutWriteJPEG (readout, view, file);
+    return true;
+}
+
+// read in all chip-level JPEG files for this FPA
+bool pmFPAWriteJPEG (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file)
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+
+        pmChip *chip = fpa->chips->data[i];
+        pmChipWriteJPEG (chip, view, file);
+    }
+    return true;
+}
+
+// read in all cell-level JPEG files for this chip
+bool pmChipWriteJPEG (pmChip *chip, const pmFPAview *view, pmFPAfile *file)
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    for (int i = 0; i < chip->cells->n; i++) {
+
+        pmCell *cell = chip->cells->data[i];
+        pmCellWriteJPEG (cell, view, file);
+    }
+    return true;
+}
+
+// read in all readout-level JPEG files for this cell
+bool pmCellWriteJPEG (pmCell *cell, const pmFPAview *view, pmFPAfile *file)
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+
+        pmReadout *readout = cell->readouts->data[i];
+        pmReadoutWriteJPEG (readout, view, file);
+    }
+    return true;
+}
+
+// read in all readout-level Objects files for this cell
+bool pmReadoutWriteJPEG (pmReadout *readout, const pmFPAview *view, pmFPAfile *file)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    switch (file->type) {
+    case PM_FPA_FILE_JPEG: {
+            // XXX we need to decide where the scale will come from in the long term
+            psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+            psStats *stats = psImageBackground(readout->image, NULL, 0, 0.25, 0.75, 10000, rng);
+            psFree(rng);
+
+            if (!isfinite(stats->robustMedian) || !isfinite(stats->robustUQ) || !isfinite(stats->robustLQ)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to measure statistics for image.\n");
+                psFree(stats);
+                return false;
+            }
+            float delta = stats->robustUQ - stats->robustLQ; // Inter-quartile range
+            float min, max;                 // Minimum and maximum for stretch
+
+            psString mode = pmFPAfileNameFromRule (file->extrule, file, view);
+            psString word = pmFPAfileNameFromRule (file->extxtra, file, view);
+            psArray *range = psStringSplitArray (word, ":", false);
+
+            if (!strcasecmp (mode, "RANGE") && range) {
+                float fmin = atof(range->data[0]);
+                float fmax = atof(range->data[1]);
+                min = stats->robustMedian + fmin*delta;
+                max = stats->robustMedian + fmax*delta;
+            } else if (!strcasecmp (mode, "FRACTION")) {
+                float fmin = atof(range->data[0]);
+                float fmax = atof(range->data[1]);
+                min = fmin*stats->robustMedian;
+                max = fmax*stats->robustMedian;
+            } else {
+                min = stats->robustMedian - 3*delta;
+                max = stats->robustMedian + 6*delta;
+            }
+            psFree(mode);
+            psFree(word);
+            psFree(range);
+            psFree(stats);
+
+            if (!isfinite(min) || !isfinite(max)) {
+                psError(PS_ERR_UNKNOWN, true, "The stretch parameters are not both finite.\n");
+                return false;
+            }
+
+            psString name = pmFPAfileNameFromRule (file->filerule, file, view);
+            psString mapname = pmFPAfileNameFromRule (file->filextra, file, view);
+            psImageJpegColormap *map = psImageJpegColormapSet (NULL, mapname);
+            psFree(mapname);
+            if (!map) {
+                map = psImageJpegColormapSet (NULL, "-greyscale");
+            }
+
+            psImageJpeg (map, readout->image, name, min, max);
+            psFree(name);
+            psFree(map);
+
+            return true;
+        }
+    default:
+        fprintf (stderr, "warning: type mismatch\n");
+        break;
+    }
+    return false;
+}
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPA_JPEG.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPA_JPEG.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPA_JPEG.h	(revision 9533)
@@ -0,0 +1,27 @@
+/** @file  pmFPAview.h
+*
+*  @brief Tools to manipulate the FPA structure elements.
+*
+*  @ingroup AstroImage
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-05-01 01:55:43 $
+*
+*  Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+*/
+
+#ifndef PM_FPA_JPEG_H
+#define PM_FPA_JPEG_H
+
+/// @addtogroup AstroImage
+/// @{
+
+bool pmFPAviewWriteJPEG (const pmFPAview *view, pmFPAfile *file);
+bool pmFPAWriteJPEG (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file);
+bool pmChipWriteJPEG (pmChip *chip, const pmFPAview *view, pmFPAfile *file);
+bool pmCellWriteJPEG (pmCell *cell, const pmFPAview *view, pmFPAfile *file);
+bool pmReadoutWriteJPEG (pmReadout *readout, const pmFPAview *view, pmFPAfile *file);
+
+# endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAfile.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAfile.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAfile.c	(revision 9533)
@@ -0,0 +1,297 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+// #include "pmFPAMaskWeight.h"
+#include "pmFPAConstruct.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmFPACopy.h"
+// #include "pmFPARead.h"
+// #include "pmFPAWrite.h"
+// #include "pmPeaks.h"
+// #include "pmMoments.h"
+// #include "pmModel.h"
+// #include "pmSource.h"
+// #include "pmSourceIO.h"
+// #include "pmGrowthCurve.h"
+// #include "pmPSF.h"
+// #include "pmPSF_IO.h"
+// #include "pmFPA_JPEG.h"
+
+static void pmFPAfileFree(pmFPAfile *file)
+{
+    if (!file) {
+        return;
+    }
+
+    psTrace ("pmFPAfileFree", 5, "freeing %s\n", file->name);
+    psFree (file->fpa);
+    psFree (file->readout);
+    psFree (file->names);
+
+    psFree (file->camera);
+    psFree (file->format);
+    psFree (file->name);
+
+    if (file->fits != NULL) {
+        psFitsClose (file->fits);
+    }
+
+    psFree (file->filerule);
+    psFree (file->filextra);
+    psFree (file->extrule);
+    psFree (file->extxtra);
+
+    psFree (file->filename);
+    psFree (file->extname);
+
+    return;
+}
+
+pmFPAfile *pmFPAfileAlloc()
+{
+    pmFPAfile *file = psAlloc(sizeof(pmFPAfile));
+    psMemSetDeallocator(file, (psFreeFunc) pmFPAfileFree);
+
+    file->phu = NULL;
+    file->readout = NULL;
+    file->header = NULL;
+
+    file->fpa = NULL;
+    file->fits = NULL;
+    file->names = psMetadataAlloc();
+
+    file->camera = NULL;
+    file->format = NULL;
+    file->name = NULL;
+
+    file->filerule = NULL;
+    file->filextra = NULL;
+    file->extrule  = NULL;
+    file->extxtra  = NULL;
+
+    file->filename = NULL;
+    file->extname  = NULL;
+
+    file->type = PM_FPA_FILE_NONE;
+    file->mode = PM_FPA_MODE_NONE;
+    file->state = PM_FPA_STATE_CLOSED;
+
+    file->xBin = 1;
+    file->yBin = 1;
+    file->src = NULL;
+
+    file->save = false;
+
+    return file;
+}
+
+// select the readout from the named pmFPAfile; if the named file does not exist,
+pmReadout *pmFPAfileThisReadout (psMetadata *files, const pmFPAview *view, const char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(files, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(name, false);
+    PS_ASSERT_INT_POSITIVE(strlen(name), false);
+
+    bool status;
+
+    pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
+    if (file == NULL) {
+        return NULL;
+    }
+
+    // internal files have the readout as a separate element:
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+        return file->readout;
+    }
+
+    pmReadout *readout = pmFPAviewThisReadout (view, file->fpa);
+    return readout;
+}
+
+// select the rule from the camera configuration, perform substitutions as needed
+char *pmFPAfileNameFromRule (char *rule, pmFPAfile *file, const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(rule, NULL);
+    PS_ASSERT_INT_POSITIVE(strlen(rule), NULL);
+    PS_ASSERT_PTR_NON_NULL(file, NULL);
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+
+    char *newName = NULL;     // destination for resulting name
+
+    newName = psStringCopy (rule);
+
+    if (strstr (newName, "{OUTPUT}") != NULL) {
+        char *name = psMetadataLookupStr (NULL, file->names, "OUTPUT");
+        if (name != NULL) {
+            newName = psStringSubstitute (newName, name, "{OUTPUT}");
+        }
+    }
+    if (strstr (newName, "{CHIP.NAME}") != NULL) {
+        pmChip *chip = pmFPAviewThisChip (view, file->fpa);
+        if (chip != NULL) {
+            char *name = psMetadataLookupStr (NULL, chip->concepts, "CHIP.NAME");
+            if (name != NULL) {
+                newName = psStringSubstitute (newName, name, "{CHIP.NAME}");
+            }
+        }
+    }
+    if (strstr (newName, "{CHIP.N}") != NULL) {
+        char *name = NULL;
+        if (view->chip < 0) {
+            psStringAppend (&name, "XX");
+        } else {
+            psStringAppend (&name, "%02d", view->chip);
+        }
+        newName = psStringSubstitute (newName, name, "{CHIP.N}");
+        psFree (name);
+    }
+    if (strstr (newName, "{CELL.NAME}") != NULL) {
+        pmCell *cell = pmFPAviewThisCell (view, file->fpa);
+        if (cell != NULL) {
+            char *name = psMetadataLookupStr (NULL, cell->concepts, "CELL.NAME");
+            if (name != NULL) {
+                newName = psStringSubstitute (newName, name, "{CELL.NAME}");
+            }
+        }
+    }
+    if (strstr (newName, "{CELL.N}") != NULL) {
+        char *name = NULL;
+        if (view->cell < 0) {
+            psStringAppend (&name, "XX");
+        } else {
+            psStringAppend (&name, "%02d", view->cell);
+        }
+        newName = psStringSubstitute (newName, name, "{CELL.N}");
+    }
+    if (strstr (newName, "{EXTNAME}") != NULL) {
+        pmHDU *hdu = pmFPAviewThisHDU (view, file->fpa);
+        if (hdu->extname && *hdu->extname) {
+            newName = psStringSubstitute (newName, hdu->extname, "{EXTNAME}");
+        }
+    }
+    if (strstr (newName, "{FILTER}") != NULL) {
+        if (file->fpa != NULL) {
+            char *name = psMetadataLookupStr (NULL, file->fpa->concepts, "FPA.FILTER");
+            if (name && *name) {
+                newName = psStringSubstitute (newName, name, "{FILTER}");
+            }
+        }
+    }
+    if (strstr (newName, "{CAMERA}") != NULL) {
+        if (file->fpa != NULL) {
+            // XXX this concept does not exist...
+            // char *name = psMetadataLookupStr (NULL, file->fpa->concepts, "FPA.CAMERA");
+            char *name = psStringCopy ("isp");
+            if (name && *name) {
+                newName = psStringSubstitute (newName, name, "{CAMERA}");
+            }
+            psFree (name);
+        }
+    }
+    if (strstr (newName, "{FILTER.ID}") != NULL) {
+        if (file->fpa != NULL) {
+            char *filterName = psMetadataLookupStr (NULL, file->fpa->concepts, "FPA.FILTER");
+            psMetadata *filterTable = psMetadataLookupPtr (NULL, file->camera, "FILTER.ID");
+            if (filterName && *filterName && filterTable) {
+                char *ID = psMetadataLookupStr (NULL, filterTable, filterName);
+                if (ID && *ID) {
+                    newName = psStringSubstitute (newName, ID, "{FILTER.ID}");
+                }
+            }
+        }
+    }
+    return newName;
+}
+
+// given an already-opened fits file, write the components corresponding
+// to the specified view
+bool pmFPAfileCopyView (pmFPA *out, pmFPA *in, const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(out, false);
+    PS_ASSERT_PTR_NON_NULL(in, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    // pmFPAWrite takes care of all PHUs as needed
+    if (view->chip == -1) {
+        pmFPACopy (out, in);
+        return true;
+    }
+    if (view->chip >= in->chips->n) {
+        psError(PS_ERR_IO, true, "Requested chip == %d >= in->chips->n == %ld", view->chip, in->chips->n);
+        return false;
+    }
+    pmChip *inChip = in->chips->data[view->chip];
+    pmChip *outChip = out->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        pmChipCopy (outChip, inChip);
+        return true;
+    }
+    if (view->cell >= inChip->cells->n) {
+        psError(PS_ERR_IO, true, "Requested cell == %d>= inChip->cells->n == %ld",
+                view->cell, inChip->cells->n);
+        return false;
+    }
+    pmCell *inCell = inChip->cells->data[view->cell];
+    pmCell *outCell = outChip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        pmCellCopy (outCell, inCell);
+        return true;
+    }
+    psError(PS_ERR_UNKNOWN, true, "Returning false");
+    return false;
+
+    // XXX add readout / segment equivalents
+}
+
+// given an already-opened fits file, write the components corresponding
+// to the specified view
+bool pmFPAfileCopyStructureView (pmFPA *out, pmFPA *in, int xBin, int yBin, const pmFPAview *view)
+{
+    bool status;
+    PS_ASSERT_PTR_NON_NULL(out, false);
+    PS_ASSERT_PTR_NON_NULL(in, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    // pmFPAWrite takes care of all PHUs as needed
+    if ((view->chip == -1) || in->hdu) {
+        status = pmFPACopyStructure (out, in, xBin, yBin);
+        return status;
+    }
+    if (view->chip >= in->chips->n) {
+        psError(PS_ERR_IO, true, "Requested chip == %d >= in->chips->n == %ld", view->chip, in->chips->n);
+        return false;
+    }
+    pmChip *inChip = in->chips->data[view->chip];
+    pmChip *outChip = out->chips->data[view->chip];
+
+    if ((view->cell == -1) || inChip->hdu) {
+        status = pmChipCopyStructure (outChip, inChip, xBin, yBin);
+        return status;
+    }
+    if (view->cell >= inChip->cells->n) {
+        psError(PS_ERR_IO, true, "Requested cell == %d>= inChip->cells->n == %ld",
+                view->cell, inChip->cells->n);
+        return false;
+    }
+    pmCell *inCell = inChip->cells->data[view->cell];
+    pmCell *outCell = outChip->cells->data[view->cell];
+
+    if ((view->readout == -1) || inCell->hdu) {
+        status = pmCellCopyStructure (outCell, inCell, xBin, yBin);
+        return status;
+    }
+    psError(PS_ERR_UNKNOWN, true, "Returning false");
+    return false;
+}
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAfile.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAfile.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAfile.h	(revision 9533)
@@ -0,0 +1,112 @@
+/** @file  pmFPAview.h
+*
+*  @brief Tools to manipulate the FPA structure elements.
+*
+*  @ingroup AstroImage
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-06-28 19:22:11 $
+*
+*  Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+*/
+
+#ifndef PM_FPA_FILE_H
+#define PM_FPA_FILE_H
+
+#include "pslib.h"
+#include "pmConfig.h"
+#include "pmFPA.h"
+#include "pmFPAview.h"
+
+typedef enum {
+    PM_FPA_BEFORE,
+    PM_FPA_AFTER,
+} pmFPAfilePlace;
+
+typedef enum {
+    PM_FPA_FILE_NONE,
+    PM_FPA_FILE_SX,
+    PM_FPA_FILE_OBJ,
+    PM_FPA_FILE_CMP,
+    PM_FPA_FILE_CMF,
+    PM_FPA_FILE_RAW,
+    PM_FPA_FILE_IMAGE,
+    PM_FPA_FILE_PSF,
+    PM_FPA_FILE_JPEG,
+} pmFPAfileType;
+
+typedef enum {
+    PM_FPA_MODE_NONE,
+    PM_FPA_MODE_READ,
+    PM_FPA_MODE_WRITE,
+    PM_FPA_MODE_INTERNAL,
+    PM_FPA_MODE_REFERENCE,
+} pmFPAfileMode;
+
+typedef enum {
+    PM_FPA_STATE_OPEN     = 0x01,
+    PM_FPA_STATE_CLOSED   = 0x02,
+    PM_FPA_STATE_INACTIVE = 0x04,
+} pmFPAfileState;
+
+typedef struct
+{
+    pmFPAfileMode mode;   // is this file read, written, or only used internally?
+    pmFPAfileType type;   // what type of data is read from / written to disk?
+    pmFPAfileState state;  // have we opened the file, etc?
+
+    pmFPALevel fileLevel;  // what level in the FPA hierarchy represents a unique file?
+    pmFPALevel dataLevel;  // at what level do we read/write the data segment?
+    pmFPALevel freeLevel;  // at what level do we read/write the data segment?
+
+    pmFPA *fpa;    // for I/O files, we carry a pointer to the complete fpa
+    psFits *fits;   // for I/O files of fits type (IMAGE, CMP, CMF), we carry a file handle
+
+    psMetadata *phu;   // pointer (view) to the current phu header
+    psMetadata *header;   // pointer (view) to the current hdu header
+
+    pmReadout *readout;   // for internal files, we only carry a single readout
+
+    psMetadata *names;   // filenames supplied by the cmdline or detdb are saved here
+
+    char *filerule;   // rule for constructing a filename when needed
+    char *filextra;   // additional information used to define filenames (context dependent)
+    char *extrule;   // rule for constructing an extension name when needed
+    char *extxtra;   // additional information used to define extension names (context dependent)
+
+    char *name;    // the name of the rule (useful for debugging / tracing)
+    char *filename;   // the current name of an active file
+    char *extname;   // the current name of an active file extension
+
+    bool save;
+
+    // the following elements are used for WRITE-mode IMAGE-type pmFPAfiles to inform
+    // the creation of a new image based on an existing image
+    pmFPA *src;    // if an output FPA, inherit from this FPA
+    int xBin;    // desired binning in x direction
+    int yBin;    // desired binning in y direction
+
+    psMetadata *camera;
+    psMetadata *format;
+}
+pmFPAfile;
+
+// allocate an empty pmFPAfile structure
+pmFPAfile *pmFPAfileAlloc ();
+
+// select the readout from the named pmFPAfile; if the named file does not exist,
+pmReadout *pmFPAfileThisReadout (psMetadata *files, const pmFPAview *view, const char *name);
+
+// add the specified filename info (value) to the files of the given mode using the given reference name
+bool pmFPAfileAddFileNames (psMetadata *files, char *name, char *value, int mode);
+
+// convert the rule to a name based on the current view
+char *pmFPAfileNameFromRule (char *rule, pmFPAfile *file, const pmFPAview *view);
+
+bool pmFPAfileCopyView (pmFPA *out, pmFPA *in, const pmFPAview *view);
+
+bool pmFPAfileCopyStructureView (pmFPA *out, pmFPA *in, int xBin, int yBin, const pmFPAview *view);
+
+# endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAfileDefine.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAfileDefine.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAfileDefine.c	(revision 9533)
@@ -0,0 +1,768 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmFPAConstruct.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmFPAfile.h"
+#include "pmFPACopy.h"
+#include "pmFPARead.h"
+#include "pmFPAWrite.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourceIO.h"
+#include "pmGrowthCurve.h"
+#include "pmPSF.h"
+#include "pmPSF_IO.h"
+#include "pmFPA_JPEG.h"
+#include "pmDetrendDB.h"
+
+// define an input-type pmFPAfile, bind to the optional fpa if supplied
+pmFPAfile *pmFPAfileDefineInput(pmConfig *config, pmFPA *fpa, char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->files, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->camera, NULL);
+    PS_ASSERT_PTR_NON_NULL(name, NULL);
+    PS_ASSERT_INT_POSITIVE(strlen(name), NULL);
+
+    bool status;
+    char *type;
+
+    // select the FILERULES from config->camera
+    psMetadata *filerules = psMetadataLookupPtr (&status, config->camera, "FILERULES");
+    if (filerules == NULL) {
+        psError(PS_ERR_IO, true, "Can't find FILERULES in the CAMERA configuration!");
+        return NULL;
+    }
+
+    // select the name from the FILERULES
+    // check for alias name (type == STR, name is aliased name)
+    char *realname = psMetadataLookupStr (&status, filerules, name);
+    if (!realname || strlen(realname) == 0) {
+        realname = name;
+    }
+
+    psMetadata *data = psMetadataLookupPtr (&status, filerules, realname);
+    if (data == NULL) {
+        psError(PS_ERR_IO, true, "Can't find file concept %s!", name);
+        return NULL;
+    }
+
+    pmFPAfile *file = pmFPAfileAlloc ();
+
+    // save the name of this pmFPAfile
+    file->name = psStringCopy (name);
+
+    file->filerule = psMemIncrRefCounter(psMetadataLookupStr (&status, data, "FILENAME.RULE"));
+    file->filextra = psMemIncrRefCounter(psMetadataLookupStr (&status, data, "FILENAME.XTRA"));
+    file->extrule  = psMemIncrRefCounter(psMetadataLookupStr (&status, data, "EXTNAME.RULE"));
+    file->extxtra  = psMemIncrRefCounter(psMetadataLookupStr (&status, data, "EXTNAME.XTRA"));
+
+    file->type = PM_FPA_FILE_NONE;
+    type = psMetadataLookupStr (&status, data, "FILE.TYPE");
+    if (type != NULL) {
+        if (!strcasecmp (type, "SX"))     {
+            file->type = PM_FPA_FILE_SX;
+        } else if (!strcasecmp (type, "OBJ"))     {
+            file->type = PM_FPA_FILE_OBJ;
+        } else if (!strcasecmp (type, "CMP"))     {
+            file->type = PM_FPA_FILE_CMP;
+        } else if (!strcasecmp (type, "CMF"))     {
+            file->type = PM_FPA_FILE_CMF;
+        } else if (!strcasecmp (type, "RAW"))     {
+            file->type = PM_FPA_FILE_RAW;
+        } else if (!strcasecmp (type, "IMAGE"))     {
+            file->type = PM_FPA_FILE_IMAGE;
+        } else if (!strcasecmp (type, "PSF"))     {
+            file->type = PM_FPA_FILE_PSF;
+        } else if (!strcasecmp (type, "JPEG"))     {
+            file->type = PM_FPA_FILE_JPEG;
+        }
+    }
+    if (file->type == PM_FPA_FILE_NONE) {
+        psError(PS_ERR_IO, true, "FILE.TYPE is not defined for %s\n", name);
+        return NULL;
+    }
+
+    file->mode = PM_FPA_MODE_READ;
+    file->fileLevel = PM_FPA_LEVEL_NONE; // the fileLevel depends on the input data
+
+    file->dataLevel = pmFPALevelFromName(psMetadataLookupStr (&status, data, "DATA.LEVEL"));
+    if (file->dataLevel == PM_FPA_LEVEL_NONE) {
+        psError(PS_ERR_IO, true, "DATA.LEVEL is not set for %s\n", name);
+        return NULL;
+    }
+    // default is to free the data after use (after written out)
+    // this can be overridden for pmFPAfiles used as carriers as well
+    file->freeLevel = file->dataLevel;
+
+    if (fpa != NULL) {
+        file->fpa = psMemIncrRefCounter(fpa);
+        file->camera = psMemIncrRefCounter((psMetadata *)fpa->camera);
+    }
+
+    // XXX ppImage and similar require the added file to be unique
+    // XXX ppFocus wants to override the selection with the new selection
+    // XXX require programs like ppFocus to remove existing files by hand
+    if (!psMetadataAddPtr (config->files, PS_LIST_TAIL, name, PS_DATA_UNKNOWN, "", file)) {
+        psError (PS_ERR_IO, false, "could not add %s to config files", name);
+        return NULL;
+    }
+    psFree (file);
+    return (file);
+}
+
+// define a pmFPAfile, bind to the optional fpa if supplied
+pmFPAfile *pmFPAfileDefineOutput(pmConfig *config, pmFPA *fpa, char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->files, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->camera, NULL);
+    PS_ASSERT_PTR_NON_NULL(name, NULL);
+    PS_ASSERT_INT_POSITIVE(strlen(name), NULL);
+
+    bool status;
+    char *type;
+
+    // select the FILERULES from the camera config
+    psMetadata *filerules = psMetadataLookupPtr (&status, config->camera, "FILERULES");
+    if (filerules == NULL) {
+        psError(PS_ERR_IO, true, "Can't find FILERULES in the CAMERA configuration!");
+        return NULL;
+    }
+
+    // select the name from the FILERULES
+    // check for alias name (type == STR, name is aliased name)
+    char *realname = psMetadataLookupStr (&status, filerules, name);
+    if (!realname || strlen(realname) == 0) {
+        realname = name;
+    }
+
+    psMetadata *data = psMetadataLookupPtr (&status, filerules, realname);
+    if (data == NULL) {
+        psError(PS_ERR_IO, true, "Can't find file concept %s!", name);
+        return NULL;
+    }
+
+    pmFPAfile *file = pmFPAfileAlloc ();
+
+    // save the name of this pmFPAfile
+    file->name = psStringCopy (name);
+
+    file->filerule = psMemIncrRefCounter(psMetadataLookupStr (&status, data, "FILENAME.RULE"));
+    file->filextra = psMemIncrRefCounter(psMetadataLookupStr (&status, data, "FILENAME.XTRA"));
+    file->extrule  = psMemIncrRefCounter(psMetadataLookupStr (&status, data, "EXTNAME.RULE"));
+    file->extxtra  = psMemIncrRefCounter(psMetadataLookupStr (&status, data, "EXTNAME.XTRA"));
+
+    file->type = PM_FPA_FILE_NONE;
+    type = psMetadataLookupStr (&status, data, "FILE.TYPE");
+    if (type != NULL) {
+        if (!strcasecmp (type, "SX"))     {
+            file->type = PM_FPA_FILE_SX;
+        } else if (!strcasecmp (type, "OBJ"))     {
+            file->type = PM_FPA_FILE_OBJ;
+        } else if (!strcasecmp (type, "CMP"))     {
+            file->type = PM_FPA_FILE_CMP;
+        } else if (!strcasecmp (type, "CMF"))     {
+            file->type = PM_FPA_FILE_CMF;
+        } else if (!strcasecmp (type, "RAW"))     {
+            file->type = PM_FPA_FILE_RAW;
+        } else if (!strcasecmp (type, "IMAGE"))     {
+            file->type = PM_FPA_FILE_IMAGE;
+        } else if (!strcasecmp (type, "PSF"))     {
+            file->type = PM_FPA_FILE_PSF;
+        } else if (!strcasecmp (type, "JPEG"))     {
+            file->type = PM_FPA_FILE_JPEG;
+        }
+    }
+    if (file->type == PM_FPA_FILE_NONE) {
+        psError(PS_ERR_IO, true, "FILE.TYPE is not defined for %s\n", name);
+        return NULL;
+    }
+
+    file->mode = PM_FPA_MODE_WRITE;
+    file->save = false;
+    char *save = psMetadataLookupStr (&status, data, "FILE.SAVE");
+    if (save != NULL) {
+        if (!strcasecmp (save, "TRUE"))     {
+            file->save = true;
+        } else if (!strcasecmp (save, "FALSE"))     {
+            file->save = false;
+        } else {
+            psError(PS_ERR_IO, true, "Illegal value \"%s\" for FILE.SAVE for %s", save, name);
+            return NULL;
+        }
+    }
+
+    file->fileLevel = pmFPALevelFromName(psMetadataLookupStr(&status, data, "FILE.LEVEL"));
+    if (file->fileLevel == PM_FPA_LEVEL_NONE) {
+        psError(PS_ERR_IO, true, "FILE.LEVEL is not set for %s\n", name);
+        return NULL;
+    }
+
+    file->dataLevel = pmFPALevelFromName(psMetadataLookupStr (&status, data, "DATA.LEVEL"));
+    if (file->dataLevel == PM_FPA_LEVEL_NONE) {
+        psError(PS_ERR_IO, true, "DATA.LEVEL is not set for %s\n", name);
+        return NULL;
+    }
+    // default is to free the data after use (after written out)
+    // this can be overridden for pmFPAfiles used as carriers as well
+    file->freeLevel = file->dataLevel;
+
+    if (fpa != NULL) {
+        file->fpa = psMemIncrRefCounter(fpa);
+    }
+
+    // for WRITE type of data, the output format needs to be determined
+    // this is only needed if the output file is not identical in structure to the input?
+    // for
+    char *formatLine = psMetadataLookupStr (&status, data, "FILE.FORMAT");
+    if (formatLine && strcasecmp (formatLine, "NONE")) {
+
+        char *formatName = NULL;
+
+        psArray *words = psStringSplitArray (formatLine, ":", false);
+        if (!words || !words->n) {
+            psAbort("pmFPAfileDefine", "programming error: cannot be NULL or empty!");
+        }
+        if (words->n == 1) {
+            // format selected in this camera
+            formatName = formatLine;
+            file->camera = psMemIncrRefCounter (config->camera);
+        }
+        if (words->n == 2) {
+            // select the requested camera
+            formatName = words->data[1];
+            file->camera = pmConfigCameraByName(config, words->data[0]);
+            // XXX do we need to save the camera name?
+            if (!file->camera) {
+                psError(PS_ERR_IO, false, "camera %s not found\n", (char *)words->data[0]);
+                psFree(words);
+                return NULL;
+            }
+        }
+
+        // select the format list from the selected camera
+        psMetadata *formats = psMetadataLookupMD(&status, file->camera, "FORMATS"); // List of formats
+
+        // select the desired format
+        char *formatFile = psMetadataLookupStr (&status, formats, formatName);
+        if (!formatFile) {
+            psError(PS_ERR_IO, false, "format %s for %s not defined", formatName, name);
+            psFree (file);
+            psFree (words);
+            return NULL;
+        }
+        readConfig (&file->format, formatFile, formatName);
+        psFree (words);
+    }
+
+    // add argument-supplied OUTPUT name to this file
+    char *outname = psMetadataLookupStr(&status, config->arguments, "OUTPUT");
+    psMetadataAddStr (file->names, PS_LIST_TAIL, "OUTPUT", PS_META_NO_REPLACE, "", outname);
+
+    // place the resulting file in the config system
+    psMetadataAddPtr (config->files, PS_LIST_TAIL, name, PS_DATA_UNKNOWN, "", file);
+    psFree (file); // we free this copy of file, but 'files' still has a copy
+    return (file); // the returned value is a view into the version on 'files'
+}
+
+// search for argname on the config->argument list
+// construct an FPA based on the files in this list (must represent a single FPA)
+// built the association between the FPA elements (CHIP/CELL) and the files
+// define the pmFPAfile filename and bind it to this FPA
+// save the pmFPAfile on config->files
+// return the pmFPAfile (a view to the one saved on config->files)
+pmFPAfile *pmFPAfileDefineFromArgs (bool *found, pmConfig *config, char *filename, char *argname)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(filename, NULL);
+    PS_ASSERT_INT_POSITIVE(strlen(filename), NULL);
+    PS_ASSERT_PTR_NON_NULL(argname, NULL);
+    PS_ASSERT_INT_POSITIVE(strlen(argname), NULL);
+
+    bool status;
+    pmFPA *fpa = NULL;
+    psFits *fits = NULL;
+    pmFPAfile *file = NULL;
+    psMetadata *phu = NULL;
+    psMetadata *format = NULL;
+
+    if (*found)
+        return NULL;
+
+    // we search the argument data for the named fileset (argname)
+    psArray *infiles = psMetadataLookupPtr(&status, config->arguments, argname);
+    if (!status) {
+        psTrace("psModules.camera", 5, "Failed to find %s in argument list", argname);
+        return NULL;
+    }
+    if (infiles->n < 1) {
+        psError(PS_ERR_IO, false, "Found n == %ld files in %s in arguments\n", infiles->n, argname);
+        return NULL;
+    }
+
+    // load the header of the first image
+    fits = psFitsOpen (infiles->data[0], "r");
+    phu = psFitsReadHeader (NULL, fits);
+    psFitsClose (fits);
+
+    // determine the current format from the header
+    // determine camera if not specified already
+    format = pmConfigCameraFormatFromHeader (config, phu);
+    if (!format) {
+        psError(PS_ERR_IO, false, "Failed to read CCD format from %s\n", (char *)infiles->data[0]);
+        psFree(phu);
+        return NULL;
+    }
+
+    // build the template fpa, set up the basic view
+    fpa = pmFPAConstruct (config->camera);
+    if (!fpa) {
+        psError(PS_ERR_IO, false, "Failed to construct FPA from %s", (char *)infiles->data[0]);
+        return NULL;
+    }
+
+    // load the given filerule (from config->camera) and bind it to the fpa
+    // the returned file is just a view to the entry on config->files
+    file = pmFPAfileDefineInput (config, fpa, filename);
+    if (!file) {
+        psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+        psFree(phu);
+        psFree(fpa);
+        psFree(format);
+        return NULL;
+    }
+
+    // adjust the rules to identify these files in the file->names data
+    psFree (file->filerule);
+    psFree (file->filextra);
+    file->filerule = psStringCopy ("@FILES");
+    file->filextra = psStringCopy ("{CHIP.NAME}.{CELL.NAME}");
+
+    // find the matching fileLevel
+    psMetadata *filemenu = psMetadataLookupPtr (&status, format, "FILE");
+    if (!filemenu) {
+        psError (PS_ERR_IO, true, "missing FILE in FORMAT");
+        return NULL;
+    }
+    char *levelName = psMetadataLookupStr (&status, filemenu, "PHU");
+    if (!levelName) {
+        psError (PS_ERR_IO, true, "missing PHU in FILE in FORMAT");
+        return NULL;
+    }
+    file->fileLevel = pmFPALevelFromName (levelName);
+    if (file->fileLevel == PM_FPA_LEVEL_NONE) {
+        psError (PS_ERR_IO, true, "unknown level");
+        return NULL;
+    }
+
+    // examine the list of input files and validate their cameras
+    // associated each filename with an element of the FPA
+    // save the association on file->names
+    for (int i = 0; i < infiles->n; i++) {
+        if (i > 0) {
+            fits = psFitsOpen (infiles->data[i], "r");
+            phu = psFitsReadHeader (NULL, fits);
+            pmConfigValidateCameraFormat (format, phu);
+            psFitsClose (fits);
+        }
+
+        // set the view to the corresponding entry for this phu
+        pmFPAview *view = pmFPAAddSourceFromHeader (fpa, phu, format);
+        if (!view) {
+            psError(PS_ERR_IO, false, "Unable to determine source for %s", file->name);
+            psFree(phu);
+            psFree (fpa);
+            psFree (format);
+            return NULL;
+        }
+
+        // associate the filename with the FPA element
+        char *name = pmFPAfileNameFromRule (file->filextra, file, view);
+
+        // save the name association in the pmFPAfile structure
+        psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", infiles->data[i]);
+
+        psFree (view);
+        psFree (name);
+        psFree (phu);
+    }
+    psFree (fpa);
+
+    file->format = format;
+    *found = true;
+    return file;
+}
+
+// search for argname on the config->argument list
+// construct an FPA based on the files in this list (each represents the same FPA)
+// built the association between the FPA elements (CHIP/CELL) and the files
+// define the pmFPAfile filenames and bind them to the FPAs
+// save the pmFPAfiles on config->files
+// return the pmFPAfiles (a view to the one saved on config->files)
+pmFPAfile *pmFPAfileDefineSingleFromArgs (bool *found, pmConfig *config, char *filename, char *argname, int entry)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(filename, NULL);
+    PS_ASSERT_INT_POSITIVE(strlen(filename), NULL);
+    PS_ASSERT_PTR_NON_NULL(argname, NULL);
+    PS_ASSERT_INT_POSITIVE(strlen(argname), NULL);
+
+    bool status;
+    pmFPA *fpa = NULL;
+    psFits *fits = NULL;
+    pmFPAfile *file = NULL;
+    psMetadata *phu = NULL;
+    psMetadata *format = NULL;
+
+    if (*found)
+        return NULL;
+
+    // we search the argument data for the named fileset (argname)
+    psArray *infiles = psMetadataLookupPtr(&status, config->arguments, argname);
+    if (!status) {
+        psTrace("psModules.camera", 5, "Failed to find %s in argument list", argname);
+        return NULL;
+    }
+    if (infiles->n <= entry) {
+        psError(PS_ERR_IO, false, "only %ld files in %s in argument, entry %d requested\n", infiles->n, argname, entry);
+        return NULL;
+    }
+
+    // examine the list of input files and validate their cameras
+    // associated each filename with an element of the FPA
+    // save the association on file->names
+    fits = psFitsOpen (infiles->data[entry], "r");
+    phu = psFitsReadHeader (NULL, fits);
+    psFitsClose (fits);
+
+    // on first call to this function, config->camera is not set.
+    // later calls will give an error if the cameras do not match
+    format = pmConfigCameraFormatFromHeader (config, phu);
+    if (!format) {
+        psError(PS_ERR_IO, false, "Failed to read CCD format from %s\n", (char *)infiles->data[0]);
+        psFree(phu);
+        return NULL;
+    }
+
+    // build the template fpa, set up the basic view
+    fpa = pmFPAConstruct (config->camera);
+    if (!fpa) {
+        psError(PS_ERR_IO, false, "Failed to construct FPA from %s", (char *)infiles->data[0]);
+        return NULL;
+    }
+
+    // load the given filerule (from config->camera) and bind it to the fpa
+    // the returned file is just a view to the entry on config->files
+    // we need a variable name here... (but in filerule)
+    file = pmFPAfileDefineInput (config, fpa, filename);
+    if (!file) {
+        psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+        psFree(phu);
+        psFree(fpa);
+        psFree(format);
+        return NULL;
+    }
+    file->format = format;
+
+    // adjust the rules to identify these files in the file->names data
+    psFree (file->filerule);
+    psFree (file->filextra);
+    file->filerule = psStringCopy ("@FILES");
+    file->filextra = psStringCopy ("{CHIP.NAME}.{CELL.NAME}");
+
+    // find the matching fileLevel
+    psMetadata *filemenu = psMetadataLookupPtr (&status, format, "FILE");
+    if (!filemenu) {
+        psError (PS_ERR_IO, true, "missing FILE in FORMAT");
+        return NULL;
+    }
+    char *levelName = psMetadataLookupStr (&status, filemenu, "PHU");
+    if (!levelName) {
+        psError (PS_ERR_IO, true, "missing PHU in FILE in FORMAT");
+        return NULL;
+    }
+    file->fileLevel = pmFPALevelFromName (levelName);
+    if (file->fileLevel == PM_FPA_LEVEL_NONE) {
+        psError (PS_ERR_IO, true, "unknown level");
+        return NULL;
+    }
+
+    // set the view to the corresponding entry for this phu
+    pmFPAview *view = pmFPAAddSourceFromHeader (fpa, phu, format);
+    if (!view) {
+        psError(PS_ERR_IO, false, "Unable to determine source for %s", file->name);
+        psFree(phu);
+        psFree (fpa);
+        psFree (format);
+        return NULL;
+    }
+
+    // associate the filename with the FPA element
+    char *name = pmFPAfileNameFromRule (file->filextra, file, view);
+
+    // save the name association in the pmFPAfile structure
+    psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", infiles->data[entry]);
+
+    psFree (view);
+    psFree (name);
+    psFree (phu);
+    psFree (fpa);
+
+    file->format = format;
+    *found = true;
+    return file;
+}
+
+// define the named pmFPAfile from the camera->config
+// only valid for pmFPAfile->mode = READ
+pmFPAfile *pmFPAfileDefineFromConf (bool *found, pmConfig *config, char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(filename, false);
+    PS_ASSERT_INT_POSITIVE(strlen(filename), false);
+
+    if (*found)
+        return NULL;
+
+    // a camera config is needed (as source of file rule)
+    if (config->camera == NULL) {
+        psError(PS_ERR_IO, true, "camera is not defined");
+        return NULL;
+    }
+
+    // build the template fpa, set up the basic view
+    pmFPA *fpa = pmFPAConstruct (config->camera);
+    if (!fpa) {
+        psError(PS_ERR_IO, false, "Failed to construct FPA for %s", filename);
+        return NULL;
+    }
+
+    // load the given filerule (from config->camera) and bind it to the fpa
+    // the returned file is just a view to the entry on config->files
+    pmFPAfile *file = pmFPAfileDefineInput (config, fpa, filename);
+    if (!file) {
+        psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+        psFree(fpa);
+        psFree(file);
+        return NULL;
+    }
+
+
+    // image names may not come from file->names
+    if (!strcasecmp (file->filerule, "@FILES")) {
+        psError(PS_ERR_IO, true, "supplied filerule uses illegal value @FILES");
+        // XXX remove the file from config->files
+        psFree(fpa);
+        psFree(file);
+        return NULL;
+    }
+
+    // image names may come from the detrend database
+    if (!strcasecmp (file->filerule, "@DETDB")) {
+        psTrace ("pmFPAfile", 5, "requiring use of detrend database source\n");
+        // don't free the file here: it is left on config->files
+        // to be used optionally by pmFPAfileDefineFromDetDB (or others)
+        psFree(fpa);
+        return NULL;
+    }
+
+    if (found) {
+        *found = true;
+    }
+    psFree (fpa);
+    return file;
+}
+
+// construct an FPA based on the supplied config->camera
+// built the association between the FPA elements (CHIP/CELL) and the files
+// define the pmFPAfile filename and bind it to this FPA
+// save the pmFPAfile on config->files
+// return the pmFPAfile (a view to the one saved on config->files)
+pmFPAfile *pmFPAfileDefineFromDetDB (bool *found, pmConfig *config, char *filename, pmFPA *input, pmDetrendType type)
+{
+    PS_ASSERT_PTR_NON_NULL(input, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->camera, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->files, NULL);
+    PS_ASSERT_PTR_NON_NULL(filename, NULL);
+
+    pmFPA *fpa = NULL;
+    pmFPAfile *file = NULL;
+
+    if (*found)
+        return NULL;
+
+    // a camera config is needed (as source of file rule)
+    if (config->camera == NULL) {
+        psError(PS_ERR_IO, true, "camera is not defined");
+        return NULL;
+    }
+    // a camera config is needed (as source of file rule)
+    if (config->cameraName == NULL) {
+        psAbort ("pmFPAfileDefine", "camera defined but not cameraName!");
+    }
+
+    // find or define a pmFPAfile with this name
+    file = psMetadataLookupPtr (NULL, config->files, filename);
+    if (!file) {
+        // build the template fpa, set up the basic view
+        fpa = pmFPAConstruct (config->camera);
+        if (!fpa) {
+            psError(PS_ERR_IO, false, "Failed to construct FPA for %s", filename);
+            return NULL;
+        }
+        // load the given filerule (from config->camera) and bind it to the fpa
+        // the returned file is just a view to the entry on config->files
+        file = pmFPAfileDefineInput (config, fpa, filename);
+        if (!file) {
+            psError(PS_ERR_IO, false, "file %s not defined\n", filename);
+            psFree (fpa);
+            return NULL;
+        }
+    }
+
+    // detselect -camera (camera) -time (time) -type (type) [others]
+    // camera and time are functions of (pmFPA *input)
+    // XXX add other options here f(pmFPA *input, type)
+
+    // Get the time from FPA.TIME
+    psTime *time = psMetadataLookupPtr(NULL, fpa->concepts, "FPA.TIME");
+    if (time->sec == 0 && time->nsec == 0) {
+        psError(PS_ERR_UNKNOWN, false, "FPA.TIME has not been set.\n");
+        psFree(fpa);
+        return NULL;
+    }
+    pmDetrendSelectOptions *options = pmDetrendSelectOptionsAlloc(config->cameraName, *time, type);
+    psFree (time);
+
+    // search for existing detrend data (detID)
+    pmDetrendSelectResults *results = pmDetrendSelect (options);
+    if (!results) {
+        psError (PS_ERR_IO, false, "no matching detrend data");
+        return NULL;
+    }
+
+    // replace the existing value of filextra with the selected detID
+    psFree (file->filextra);
+    file->filextra  = psStringCopy (results->detID);
+
+    // set the fileLevel based on the detDB results
+    file->fileLevel = results->level;
+
+    psFree (results);
+    psFree (options);
+
+    *found = true;
+    return file;
+}
+
+// create a new output pmFPAfile based on an existing FPA
+// only valid for pmFPAfile->mode == WRITE (or internal?)
+pmFPAfile *pmFPAfileDefineFromFPA (pmConfig *config, pmFPA *src, int xBin, int yBin, char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(src, false);
+    PS_ASSERT_PTR_NON_NULL(filename, false);
+    PS_ASSERT_INT_POSITIVE(strlen(filename), false);
+
+    pmFPA *fpa = pmFPAConstruct (src->camera);
+    pmFPAfile *file = pmFPAfileDefineOutput (config, fpa, filename);
+    if (!file) {
+        psErrorStackPrint(stderr, "file %s not defined\n", filename);
+        return NULL;
+    }
+    file->src = src; // inherit output elements from this source pmFPA
+    file->xBin = xBin;
+    file->yBin = yBin;
+
+    psFree (fpa);
+    return file;
+}
+
+// create a new output pmFPAfile based on an existing FPA
+// only valid for pmFPAfile->mode == WRITE (or internal?)
+pmFPAfile *pmFPAfileDefineNewCamera (pmConfig *config, char *filename)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(filename, false);
+    PS_ASSERT_INT_POSITIVE(strlen(filename), false);
+
+    pmFPAfile *file = pmFPAfileDefineOutput (config, NULL, filename);
+    if (!file) {
+        psErrorStackPrint(stderr, "file %s not defined\n", filename);
+        return NULL;
+    }
+    if (!file->camera) {
+        psErrorStackPrint(stderr, "file %s does not define a new camera\n", filename);
+        return NULL;
+    }
+    file->fpa = pmFPAConstruct(file->camera);
+
+    return file;
+}
+
+// create a file with the given name, assign it type "INTERNAL", and supply it with an image
+// of the requested dimensions. (image only, mask and weight are ignored)
+pmReadout *pmFPAfileDefineInternal (psMetadata *files, char *name, int Nx, int Ny, int type)
+{
+    PS_ASSERT_PTR_NON_NULL(files, false);
+    PS_ASSERT_PTR_NON_NULL(name, false);
+    PS_ASSERT_INT_POSITIVE(strlen(name), false);
+
+    pmReadout *readout = pmReadoutAlloc(NULL);
+    readout->image = psImageAlloc(Nx, Ny, type);
+
+    // I want an image from the
+    pmFPAfile *file = pmFPAfileAlloc();
+    file->mode = PM_FPA_MODE_INTERNAL;
+    file->name = psStringCopy (name);
+
+    file->readout = readout;
+    psMetadataAddPtr(files, PS_LIST_TAIL, name, PS_DATA_UNKNOWN, "", file);
+    psFree(file);
+    // we free this copy of file, but 'files' still has a copy
+
+    return readout;
+}
+
+bool pmFPAfileDropInternal(psMetadata *files, char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(files, false);
+    PS_ASSERT_PTR_NON_NULL(name, false);
+    PS_ASSERT_INT_POSITIVE(strlen(name), false);
+
+    bool status = false;
+
+    pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
+    if (!status) {
+        psTrace("psModules.camera", 6, "Internal File %s not in file list", name);
+        return true;
+    }
+    if (file == NULL) {
+        psError(PS_ERR_IO, true, "file %s is NULL", name);
+        return false;
+    }
+    if (file->mode != PM_FPA_MODE_INTERNAL) {
+        psTrace("psModules.camera", 6, "FPA File %s not Internal, not dropping", name);
+        return true;
+    }
+
+    psTrace("psModules.camera", 6, "dropping Internal FPA File %s", name);
+    psMetadataRemoveKey (files, name);
+    return true;
+}
+
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAfileDefine.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAfileDefine.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAfileDefine.h	(revision 9533)
@@ -0,0 +1,51 @@
+/** @file  pmFPAview.h
+*
+*  @brief Tools to manipulate the FPA structure elements.
+*
+*  @ingroup AstroImage
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-06-28 19:23:20 $
+*
+*  Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+*/
+
+#include <pmDetrendDB.h>
+
+#ifndef PM_FPA_FILE_DEFINE_H
+#define PM_FPA_FILE_DEFINE_H
+
+// load the pmFPAfile information from the camera configuration data
+pmFPAfile *pmFPAfileDefineInput (pmConfig *config, pmFPA *fpa, char *name);
+
+// load the pmFPAfile information from the camera configuration data
+pmFPAfile *pmFPAfileDefineOutput (pmConfig *config, pmFPA *fpa, char *name);
+
+// look for the given argname on the argument list.  find the give filename from the file rules
+pmFPAfile *pmFPAfileDefineFromArgs (bool *found, pmConfig *config, char *filename, char *argname);
+
+// look for the given argname on the argument list.  find the give filename from the file rules
+pmFPAfile *pmFPAfileDefineFromConf (bool *found, pmConfig *config, char *filename);
+
+// look for the given argname on the argument list.  find the give filename from the file rules
+pmFPAfile *pmFPAfileDefineFromDetDB (bool *found, pmConfig *config, char *filename, pmFPA *input, pmDetrendType type);
+
+// create a new output pmFPAfile based on an existing FPA
+pmFPAfile *pmFPAfileDefineFromFPA (pmConfig *config, pmFPA *src, int xBin, int yBin, char *filename);
+
+// create a new output pmFPAfile based on an existing FPA
+// only valid for pmFPAfile->mode == WRITE (or internal?)
+pmFPAfile *pmFPAfileDefineNewCamera (pmConfig *config, char *filename);
+
+// create a file with the given name, assign it type "INTERNAL", and supply it with an image
+// of the requested dimensions. (image only, mask and weight are ignored)
+pmReadout *pmFPAfileDefineInternal (psMetadata *files, char *name, int Nx, int Ny, int type);
+
+// delete the INTERNAL file of the given name (if it exists)
+bool pmFPAfileDropInternal (psMetadata *files, char *name);
+
+// look for the given argname on the argument list.  find the give filename from the file rules
+pmFPAfile *pmFPAfileDefineSingleFromArgs (bool *found, pmConfig *config, char *filename, char *argname, int entry);
+# endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAfileFitsIO.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAfileFitsIO.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAfileFitsIO.c	(revision 9533)
@@ -0,0 +1,227 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPARead.h"
+#include "pmFPAWrite.h"
+#include "pmFPAMaskWeight.h"
+#include "pmFPAConstruct.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmFPAfileFitsIO.h"
+
+// given an already-opened fits file, read the components corresponding
+// to the specified view
+bool pmFPAviewReadFitsImage (const pmFPAview *view, pmFPAfile *file)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    bool status;
+    pmFPA *fpa = file->fpa;
+    psFits *fits = file->fits;
+
+    if (view->chip == -1) {
+        status = pmFPARead (fpa, fits, NULL);
+        return status;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_IO, true, "Requested chip == %d >= fpa->chips->n == %ld", view->chip, fpa->chips->n);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        status = pmChipRead (chip, fits, NULL);
+        return status;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_IO, true, "Requested cell == %d >= chip->cells->n == %ld", view->cell, chip->cells->n);
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        status = pmCellRead (cell, fits, NULL);
+        return status;
+    }
+    psError(PS_ERR_UNKNOWN, true, "Returning false");
+    return false;
+
+    // XXX pmReadoutRead, pmReadoutReadSegement disabled for now
+    #if 0
+
+    if (view->readout >= cell->readouts->n) {
+        psError(PS_ERR_IO, true, "Requested readout == %d >= cell->readouts->n == %d",
+                view->readout, cell->readouts->n);
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    if (view->nRows == 0) {
+        pmReadoutRead (readout, fits, NULL);
+    } else {
+        pmReadoutReadSegment (readout, fits, view->nRows, view->iRows, NULL, NULL);
+    }
+    return true;
+    #endif
+}
+
+// given an already-opened fits file, write the components corresponding
+// to the specified view. when the file was opened, pmFPA/Chip/CellWrite was
+// called on it with blank=true to write the (possible) blank PHU
+// do NOT call the functions below with blank=true or they will write
+// out data in an inconsistent fashion
+// the calls below should recurse down the element to write out all components.
+bool pmFPAviewWriteFitsImage (const pmFPAview *view, pmFPAfile *file)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    pmFPA *fpa = file->fpa;
+    psFits *fits = file->fits;
+
+    // pmFPAWrite takes care of all PHUs as needed
+    if (view->chip == -1) {
+        pmFPAWrite(fpa, fits, NULL, false, true);
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_IO, true, "Requested chip == %d >= fpa->chips->n == %ld", view->chip, fpa->chips->n);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        pmChipWrite (chip, fits, NULL, false, true);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_IO, true, "Requested cell == %d >= chip->cells->n == %ld", view->cell, chip->cells->n);
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        return pmCellWrite (cell, fits, NULL, false);
+    }
+    psError(PS_ERR_UNKNOWN, true, "Returning false");
+    return false;
+
+    // XXX disable readout write for now
+    #if 0
+
+    if (view->readout >= cell->readouts->n) {
+        psError(PS_ERR_IO, true, "Requested readout == %d >= cell->readouts->n == %d",
+                view->readout, cell->readouts->n);
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    if (view->nRows == 0) {
+        pmReadoutWrite (readout, fits, NULL, NULL);
+    } else {
+        pmReadoutWriteSegment (readout, fits, view->nRows, view->iRows, NULL, NULL);
+    }
+    return true;
+    #endif
+}
+
+// this old code was used to write the blank by hand.
+// pmFPAWrite now takes care of this choice.
+# if 0
+// do we need to write out a PHU for this entry?
+if (file->phu == NULL)
+    {
+        pmHDU *hdu = pmFPAviewThisHDU (view, file->fpa);
+        pmHDU *phu = pmFPAviewThisPHU (view, file->fpa);
+        // if this hdu is the phu, the write function below will create the phu
+        if (hdu != phu) {
+            // we assume that the PHU is just a header
+            // header may not be defined for constructed images; make a dummy one
+            psMetadata *outhead = NULL;
+            if (phu->header) {
+                outhead = psMetadataCopy (NULL, phu->header);
+            } else {
+                outhead = psMetadataAlloc ();
+            }
+            psMetadataAdd (outhead, PS_LIST_TAIL, "EXTEND", PS_DATA_BOOL | PS_META_REPLACE, "this file has extensions", true);
+            psFitsWriteBlank (file->fits, outhead, "");
+            file->phu = phu->header;
+            psTrace ("pmFPAfile", 5, "wrote phu %s (type: %d)\n", file->filename, file->type);
+            psFree (outhead);
+        }
+    }
+# endif
+
+// given an already-opened fits file, read the components corresponding
+// to the specified view
+bool pmFPAviewFreeFitsImage (const pmFPAview *view, pmFPAfile *file)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        psTrace ("pmFPAfile", 5, "freeing fpa for %s\n", file->filename);
+        pmFPAFreeData (fpa);
+        // XXX drop me: file->fpa = NULL;
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_IO, true, "Requested chip == %d >= fpa->chips->n == %ld", view->chip, fpa->chips->n);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        psTrace ("pmFPAfile", 5, "freeing chip %d for %s\n", view->chip, file->filename);
+        pmChipFreeData (chip);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_IO, true, "Requested cell == %d >= chip->cells->n == %ld", view->cell, chip->cells->n);
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        psTrace ("pmFPAfile", 5, "freeing cell %d for %s\n", view->cell, file->filename);
+        pmCellFreeData (cell);
+        return true;
+    }
+    psError(PS_ERR_UNKNOWN, true, "Returning false");
+    return false;
+
+    // XXX pmReadoutRead, pmReadoutReadSegement disabled for now
+    #if 0
+
+    if (view->readout >= cell->readouts->n) {
+        psError(PS_ERR_IO, true, "Requested readout == %d >= cell->readouts->n == %d",
+                view->readout, cell->readouts->n);
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    if (view->nRows == 0) {
+        pmReadoutRead (readout, fits, NULL);
+    } else {
+        pmReadoutReadSegment (readout, fits, view->nRows, view->iRows, NULL, NULL);
+    }
+    return true;
+    #endif
+}
+
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAfileFitsIO.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAfileFitsIO.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAfileFitsIO.h	(revision 9533)
@@ -0,0 +1,27 @@
+/** @file  pmFPAview.h
+*
+*  @brief Tools to manipulate the FPA structure elements.
+*
+*  @ingroup AstroImage
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-06-17 01:50:43 $
+*
+*  Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+*/
+
+#ifndef PM_FPA_FILE_FITS_IO_H
+#define PM_FPA_FILE_FITS_IO_H
+
+// read an image into the current view
+bool pmFPAviewReadFitsImage (const pmFPAview *view, pmFPAfile *file);
+
+// write the components for the specified view
+bool pmFPAviewWriteFitsImage (const pmFPAview *view, pmFPAfile *file);
+
+// free the appropriate image containers
+bool pmFPAviewFreeFitsImage (const pmFPAview *view, pmFPAfile *file);
+
+# endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAfileIO.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAfileIO.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAfileIO.c	(revision 9533)
@@ -0,0 +1,690 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmConfig.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmFPAConstruct.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+#include "pmFPACopy.h"
+#include "pmFPARead.h"
+#include "pmFPAWrite.h"
+#include "pmFPAfileFitsIO.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourceIO.h"
+#include "pmGrowthCurve.h"
+#include "pmPSF.h"
+#include "pmPSF_IO.h"
+#include "pmFPA_JPEG.h"
+# include "pmDetrendDB.h"
+
+// open file (if not already opened)
+bool pmFPAfileOpen (pmFPAfile *file, const pmFPAview *view, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    bool status;
+    char *mode = NULL;
+    char *readMode = "r";
+    char *writeMode = "w";
+
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip open for %s, files is inactive", file->name);
+        return true;
+    }
+
+    if (file->state == PM_FPA_STATE_OPEN) {
+        return true;
+    }
+
+    if (file->mode == PM_FPA_MODE_NONE) {
+        psError(PS_ERR_IO, true, "File is mode PM_FPA_MODE_NONE");
+        return false;
+    }
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+        psError(PS_ERR_IO, true, "File is mode PM_FPA_MODE_INTERNAL");
+        return false;
+    }
+    if (file->mode == PM_FPA_MODE_READ) {
+        mode = readMode;
+    }
+    if (file->mode == PM_FPA_MODE_WRITE) {
+        mode = writeMode;
+    }
+
+    // determine the file name
+    // free a name allocated earlier
+    // XXX for CMP files which defer opening, we have an inconsistent value
+    // for file->extra here and in the pmFPAfileDefine fucntions.
+    //
+    psFree (file->filename);
+    file->filename = pmFPAfileNameFromRule (file->filerule, file, view);
+    if (file->filename == NULL) {
+        psError(PS_ERR_IO, true, "Filename is NULL");
+        return false;
+    }
+
+    // indirect filenames
+    if (!strcasecmp (file->filename, "@FILES")) {
+        char *extra = pmFPAfileNameFromRule (file->filextra, file, view);
+        if (extra == NULL) {
+            psError(PS_ERR_IO, false, "error converting extrule to name %s\n", file->extrule);
+            return false;
+        }
+
+        psFree (file->filename);
+        file->filename = psMetadataLookupStr (&status, file->names, extra);
+
+        if (file->filename == NULL) {
+            psError(PS_ERR_IO, true, "filename lookup error (@FILES) for %s : %s\n", file->filextra, extra);
+            psFree (extra);
+            return false;
+        }
+        // psMetadataLookupStr just returns a view, file->filename must be protected
+        psMemIncrRefCounter (file->filename);
+        psFree (extra);
+    }
+
+    // get name from detrend database
+    // extrule expands to yield the class_id
+    // filextra contains the desired -det_id detID -iteration iter string
+    if (!strcasecmp (file->filename, "@DETDB")) {
+        char *extra = pmFPAfileNameFromRule (file->extrule, file, view);
+        if (extra == NULL) {
+            psError(PS_ERR_IO, false, "error converting extrule to name %s\n", file->extrule);
+            return false;
+        }
+        psTrace ("pmFPAfile", 6, "looking for detrend (%s, %s)\n", file->filextra, extra);
+
+        psFree (file->filename);
+
+        file->filename = pmDetrendFile (file->filextra, extra);
+        if (file->filename == NULL) {
+            psError(PS_ERR_IO, false, "failed to find a valid detrend image for detID %s : classID %s\n", file->filextra, extra);
+            psFree (extra);
+            return false;
+        }
+
+        // XXXX this is very ad-hoc
+        // XXXX prepend the value config[WORKDIR] to the filename...
+        char *workdir = psMetadataLookupStr (NULL, config->site, "WORKDIR");
+        if (workdir) {
+            psStringPrepend (&file->filename, "%s/", workdir);
+        }
+
+        psTrace ("pmFPAfile", 6, "got detrend file %s\n", file->filename);
+        psFree (extra);
+    }
+    // if () "neb://" -> get from neb...
+
+    switch (file->type) {
+        // open the FITS types:
+    case PM_FPA_FILE_IMAGE:
+        psTrace ("pmFPAfile", 5, "opening %s (type: %d)\n", file->filename, file->type);
+        file->fits = psFitsOpen (file->filename, mode);
+        if (file->fits == NULL) {
+            psError(PS_ERR_IO, false, "error opening file %s\n", file->filename);
+            return false;
+        }
+        file->state = PM_FPA_STATE_OPEN;
+
+        // In some cases, I need to call pmFPAAddSourceFromHeader after I've opened the file,
+        // specifically if I have not called this function on startup.  This happens for the
+        // images supplied by the detrend database, which are only identified here (above).
+        // this is never true for the output images, which are constructed later
+
+        if (file->mode == PM_FPA_MODE_READ) {
+            pmChip *chip;
+            pmCell *cell;
+            bool addSource = false;
+            switch (file->fileLevel) {
+            case PM_FPA_LEVEL_FPA:
+                addSource = !file->fpa->hdu;
+                break;
+            case PM_FPA_LEVEL_CHIP:
+                chip = pmFPAviewThisChip(view, file->fpa);
+                if (!chip) {
+                    psError (PS_ERR_IO, true, "inconsistent file/fpa: fileLevel is CHIP, view is FPA");
+                    return false;
+                }
+                addSource = !chip->hdu;
+                break;
+            case PM_FPA_LEVEL_CELL:
+                cell = pmFPAviewThisCell(view, file->fpa);
+                if (!cell) {
+                    psError (PS_ERR_IO, true, "inconsistent file/fpa: fileLevel is CELL, view is FPA");
+                    return false;
+                }
+                addSource = !cell->hdu;
+                break;
+            default:
+                psAbort ("pmFPAfileIO", "fileLevel not correctly set");
+                break;
+            }
+
+            if (addSource) {
+                psMetadata *phu = psFitsReadHeader (NULL, file->fits);
+                if (!file->format) {
+                    file->format = pmConfigCameraFormatFromHeader (config, phu);
+                    if (!file->format) {
+                        psError(PS_ERR_IO, false, "Failed to read CCD format from %s\n", file->filename);
+                        psFree(phu);
+                        return NULL;
+                    }
+                } else {
+                    pmConfigValidateCameraFormat (file->format, phu);
+                }
+                pmFPAview *thisView = pmFPAAddSourceFromHeader (file->fpa, phu, file->format);
+                psFree (thisView);
+                psFree (phu);
+                // XXX we can check the output view to be sure it corresponds to our current view
+            }
+        }
+        if (file->mode == PM_FPA_MODE_WRITE) {
+            switch (file->fileLevel) {
+            case PM_FPA_LEVEL_FPA:
+                pmFPAWrite (file->fpa, file->fits, NULL, true, false);
+                break;
+            case PM_FPA_LEVEL_CHIP: {
+                    pmChip *chip = pmFPAviewThisChip(view, file->fpa);
+                    pmChipWrite (chip, file->fits, NULL, true, false);
+                    break;
+                }
+            case PM_FPA_LEVEL_CELL: {
+                    pmCell *cell = pmFPAviewThisCell(view, file->fpa);
+                    pmCellWrite(cell, file->fits, NULL, true);
+                    break;
+                }
+            default:
+                psAbort ("pmFPAfileIO", "fileLevel not correctly set");
+                break;
+            }
+        }
+        break;
+
+        // open the FITS object files
+    case PM_FPA_FILE_CMF:
+        psTrace ("pmFPAfile", 5, "opening %s (type: %d)\n", file->filename, file->type);
+        file->fits = psFitsOpen (file->filename, mode);
+        if (file->fits == NULL) {
+            psError(PS_ERR_IO, false, "error opening file %s\n", file->filename);
+            return false;
+        }
+        file->state = PM_FPA_STATE_OPEN;
+        break;
+
+        // defer opening TEXT types:
+    case PM_FPA_FILE_SX:
+    case PM_FPA_FILE_OBJ:
+    case PM_FPA_FILE_CMP:
+    case PM_FPA_FILE_RAW:
+    case PM_FPA_FILE_PSF:
+    case PM_FPA_FILE_JPEG:
+        psTrace ("pmFPAfile", 5, "defer opening %s\n", file->filename);
+        break;
+
+    default:
+        psError(PS_ERR_IO, true, "type mismatch for %s : %d\n", file->filename, file->type);
+        return false;
+    }
+    return true;
+}
+
+bool pmFPAfileRead(pmFPAfile *file, const pmFPAview *view, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip read for %s, files is inactive", file->name);
+        return true;
+    }
+
+    if (file->mode != PM_FPA_MODE_READ) {
+        psTrace("psModules.camera", 6, "skip read for %s, mode is not READ", file->name);
+        return true;
+    }
+
+    // an internal file should not be returned to here
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+        psError(PS_ERR_IO, true, "File is mode PM_FPA_MODE_INTERNAL");
+        return false;
+    }
+
+    // get the current level
+    pmFPALevel level = pmFPAviewLevel (view);
+
+    // do we need to open this file?
+    if (level == file->fileLevel) {
+        if (!pmFPAfileOpen (file, view, config)) {
+            psError(PS_ERR_IO, false, "failed to open file %s", file->name);
+            return false;
+        }
+    }
+
+    // do we need to read this file?
+    if (level != file->dataLevel) {
+        psTrace("psModules.camera", 6, "skip reading of %s at this level %s: dataLevel is %s",
+                file->name, pmFPALevelToName(level), pmFPALevelToName(file->dataLevel));
+        return true;
+    }
+
+    // We need to read it --- double-check it's open!
+    if (file->state == PM_FPA_STATE_CLOSED) {
+        file->fileLevel = level;
+        if (!pmFPAfileOpen (file, view, config)) {
+            psError(PS_ERR_IO, false, "failed to open file %s when attempting to read", file->name);
+            return false;
+        }
+    }
+
+    switch (file->type) {
+    case PM_FPA_FILE_IMAGE:
+        if (pmFPAviewReadFitsImage (view, file)) {
+            psTrace ("pmFPAfile", 5, "reading %s (type: %d)\n", file->filename, file->type);
+        } else {
+            psError(PS_ERR_UNKNOWN, false, "skipping %s (type: %d)\n", file->filename, file->type);
+            return false;
+        }
+        break;
+
+    case PM_FPA_FILE_SX:
+    case PM_FPA_FILE_RAW:
+    case PM_FPA_FILE_OBJ:
+    case PM_FPA_FILE_CMP:
+    case PM_FPA_FILE_CMF:
+        pmFPAviewReadObjects (view, file);
+        psTrace ("pmFPAfile", 5, "reading %s (type: %d)\n", file->filename, file->type);
+        break;
+
+    case PM_FPA_FILE_PSF:
+        pmFPAviewReadPSFmodel (view, file);
+        psTrace ("pmFPAfile", 5, "reading %s (type: %d)\n", file->filename, file->type);
+        break;
+
+    case PM_FPA_FILE_JPEG:
+        break;
+
+    default:
+        psError(PS_ERR_IO, true, "warning: type mismatch; saw type %d", file->type);
+        return false;
+    }
+    return true;
+}
+
+bool pmFPAfileFreeData(pmFPAfile *file, const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip free for %s, files is inactive", file->name);
+        return true;
+    }
+
+    // an internal file should not be returned to here
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+        psError(PS_ERR_IO, true, "File is mode PM_FPA_MODE_INTERNAL");
+        return false;
+    }
+
+    // get the current level
+    pmFPALevel level = pmFPAviewLevel (view);
+
+    // do we need to read this file?
+    if (level != file->freeLevel) {
+        psTrace("psModules.camera", 6, "skip free of %s at this level %s: freeLevel is %s",
+                file->name, pmFPALevelToName(level), pmFPALevelToName(file->freeLevel));
+        return true;
+    }
+
+    switch (file->type) {
+    case PM_FPA_FILE_IMAGE:
+        if (pmFPAviewFreeFitsImage (view, file)) {
+            psTrace ("pmFPAfile", 5, "freed %s (type: %d)\n", file->filename, file->type);
+            if (file->filename == NULL) {
+                psTrace ("pmFPAfile", 5, "filename is not defined for %s\n", file->name);
+            }
+        } else {
+            psError(PS_ERR_UNKNOWN, false, "skipping %s (type: %d)\n", file->filename, file->type);
+            return false;
+        }
+        break;
+
+    case PM_FPA_FILE_SX:
+    case PM_FPA_FILE_RAW:
+    case PM_FPA_FILE_OBJ:
+    case PM_FPA_FILE_CMP:
+    case PM_FPA_FILE_CMF:
+        // pmFPAviewFreeObjects (view, file);
+        psTrace ("pmFPAfile", 5, "NOT freeing %s (type: %d)\n", file->filename, file->type);
+        break;
+
+        // XXX not certain what I need to free here
+    case PM_FPA_FILE_PSF:
+    case PM_FPA_FILE_JPEG:
+        break;
+
+    default:
+        psError(PS_ERR_IO, true, "warning: type mismatch; saw type %d", file->type);
+        return false;
+    }
+    return true;
+}
+
+bool pmFPAfileWrite(pmFPAfile *file, const pmFPAview *view, pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip write for %s, files is inactive", file->name);
+        return true;
+    }
+
+    if (file->mode != PM_FPA_MODE_WRITE) {
+        psTrace("psModules.camera", 6, "skip write for %s, mode is not WRITE", file->name);
+        return true;
+    }
+
+    // an internal file should not be returned to here
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+        psError(PS_ERR_IO, true, "File is mode PM_FPA_MODE_INTERNAL");
+        return false;
+    }
+
+    if (!file->save) {
+        psTrace("psModules.camera", 6, "skip write for %s, save is FALSE", file->name);
+        return true;
+    }
+
+    // get the current level
+    pmFPALevel level = pmFPAviewLevel (view);
+
+    // do we need to write this file?
+    if (level != file->dataLevel) {
+        psTrace("psModules.camera", 6, "skip writing of %s at this level %s: dataLevel is %s",
+                file->name, pmFPALevelToName(level), pmFPALevelToName(file->dataLevel));
+        return true;
+    }
+
+    // do we need to open this file?
+    if (level >= file->fileLevel) {
+        if (!pmFPAfileOpen (file, view, config)) {
+            psError(PS_ERR_IO, false, "failed to open %s", file->filename);
+            return false;
+        }
+    }
+
+    switch (file->type) {
+    case PM_FPA_FILE_IMAGE:
+        pmFPAviewWriteFitsImage (view, file);
+        psTrace ("pmFPAfile", 5, "wrote image %s (fpa: %p)\n", file->filename, file->fpa);
+        break;
+
+    case PM_FPA_FILE_SX:
+    case PM_FPA_FILE_RAW:
+    case PM_FPA_FILE_OBJ:
+    case PM_FPA_FILE_CMP:
+    case PM_FPA_FILE_CMF:
+        psTrace ("pmFPAfile", 5, "writing object %s (fpa: %p)\n", file->filename, file->fpa);
+        if (!pmFPAviewWriteObjects (view, file)) {
+            psError(PS_ERR_IO, false, "Failed to write object %s", file->filename);
+            return false;
+        }
+
+        break;
+
+    case PM_FPA_FILE_PSF:
+        pmFPAviewWritePSFmodel (view, file);
+        psTrace ("pmFPAfile", 5, "wrote PSF %s (fpa: %p)\n", file->filename, file->fpa);
+        break;
+
+    case PM_FPA_FILE_JPEG:
+        pmFPAviewWriteJPEG (view, file);
+        psTrace ("pmFPAfile", 5, "wrote PSF %s (fpa: %p)\n", file->filename, file->fpa);
+        break;
+
+    default:
+        fprintf (stderr, "warning: type mismatch\n");
+        return false;
+    }
+    return true;
+}
+
+// create the data elements (headers, images) appropriate for this view
+bool pmFPAfileCreate (pmFPAfile *file, const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    // these are not error conditions; these are state tests
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip create for inactive file %s", file->name);
+        return true;
+    }
+    if (file->mode != PM_FPA_MODE_WRITE) {
+        psTrace("psModules.camera", 6, "skip create for non-write file %s", file->name);
+        return true;
+    }
+
+    // an internal file should not be returned to here
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+        psError(PS_ERR_IO, true, "File is mode PM_FPA_MODE_INTERNAL");
+        return false;
+    }
+
+    // get the current level
+    pmFPALevel level = pmFPAviewLevel (view);
+
+    // don't create the file if the src (FPA) is not defined
+    if (file->src == NULL) {
+        psTrace("psModules.camera", 6, "skip create for FPA without src FPA for %s", file->name);
+        return true;
+    }
+
+    // do we need to write this file?
+    if (level != file->fileLevel) {
+        psTrace("psModules.camera", 6, "skip creation of %s at this level %s: fileLevel is %s",
+                file->name, pmFPALevelToName(level), pmFPALevelToName(file->fileLevel));
+        return true;
+    }
+
+    switch (file->type) {
+    case PM_FPA_FILE_IMAGE:
+
+        // create FPA structure component based on view
+        pmFPAAddSourceFromView (file->fpa, view, file->format);
+        psTrace ("pmFPAfile", 5, "created fpa data elements for %s (fpa: %p)\n", file->name, file->fpa);
+        break;
+
+    case PM_FPA_FILE_SX:
+    case PM_FPA_FILE_RAW:
+    case PM_FPA_FILE_OBJ:
+    case PM_FPA_FILE_CMP:
+    case PM_FPA_FILE_CMF:
+    case PM_FPA_FILE_PSF:
+    case PM_FPA_FILE_JPEG:
+        break;
+
+    default:
+        psError(PS_ERR_IO, true, "Unsupported type for %s: %d", file->name, file->type);
+        return false;
+    }
+    return true;
+}
+
+bool pmFPAfileClose (pmFPAfile *file, const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        psTrace("psModules.camera", 6, "skip close for %s, files is inactive", file->name);
+        return true;
+    }
+    if (file->state == PM_FPA_STATE_CLOSED) {
+        return true;
+    }
+
+    // an internal file should not be returned to here
+    if (file->mode == PM_FPA_MODE_INTERNAL) {
+        psError(PS_ERR_IO, true, "File is mode PM_FPA_MODE_INTERNAL");
+        return false;
+    }
+
+    // is current level == open level?
+    pmFPALevel level = pmFPAviewLevel (view);
+    if (file->fileLevel != level) {
+        psTrace("psModules.camera", 6, "skip closing of %s at this level %s: dataLevel is %s",
+                file->name, pmFPALevelToName(level), pmFPALevelToName(file->dataLevel));
+        return true;
+    }
+
+    // check if we are actually open
+    switch (file->type) {
+        // check the FITS types
+    case PM_FPA_FILE_IMAGE:
+    case PM_FPA_FILE_CMF:
+        psFitsClose (file->fits);
+        psTrace ("pmFPAfile", 5, "closing %s (type: %d)\n", file->filename, file->type);
+        file->fits = NULL;
+        file->phu = NULL;
+        file->header = NULL;
+        file->state = PM_FPA_STATE_CLOSED;
+        break;
+
+        // ignore the TEXT types
+    case PM_FPA_FILE_SX:
+    case PM_FPA_FILE_RAW:
+    case PM_FPA_FILE_OBJ:
+    case PM_FPA_FILE_CMP:
+    case PM_FPA_FILE_PSF:
+    case PM_FPA_FILE_JPEG:
+        break;
+
+    default:
+        psError(PS_ERR_IO, true, "type mismatch: %d", file->type);
+        return false;
+    }
+    return true;
+}
+
+// set the state of the specified pmFPAfile to active (state == true) or inactive
+// if name is NULL, set the state for all pmFPAfiles
+bool pmFPAfileActivate (psMetadata *files, bool state, char *name)
+{
+    PS_ASSERT_PTR_NON_NULL(files, false);
+
+    if (!name) {
+        psMetadataItem *item = NULL;
+        psMetadataIterator *iter = psMetadataIteratorAlloc (files, PS_LIST_HEAD, NULL);
+        while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+            pmFPAfile *file = item->data.V;
+            if (state) {
+                file->state &= NOT_U8(PM_FPA_STATE_INACTIVE);
+            } else {
+                file->state |= PM_FPA_STATE_INACTIVE;
+            }
+        }
+        psFree (iter);
+        return true;
+    }
+
+    bool status = false;
+    pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
+    if (!status) {
+        psTrace("psModules.camera", 6, "%s is not a defined IO file", name);
+        return false;
+    }
+    if (!file) {
+        psError(PS_ERR_IO, true, "file %s is NULL", name);
+        return false;
+    }
+    if (state) {
+        file->state &= NOT_U8(PM_FPA_STATE_INACTIVE);
+    } else {
+        file->state |= PM_FPA_STATE_INACTIVE;
+    }
+    return true;
+}
+
+// attempt create, read, write, close, or free pmFPAfiles available in files
+// files are automatically opened before they are read
+bool pmFPAfileIOChecks (pmConfig *config, const pmFPAview *view, pmFPAfilePlace place)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(config->files, false);
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    psMetadata *files = config->files;
+
+    // attempt to perform all create, read, write, close operations
+    psMetadataItem *item = NULL;
+    psMetadataIterator *iter = psMetadataIteratorAlloc (files, PS_LIST_HEAD, NULL);
+    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+        pmFPAfile *file = item->data.V;
+
+        switch (place) {
+        case PM_FPA_BEFORE:
+            if (!pmFPAfileRead (file, view, config)) {
+                psError(PS_ERR_IO, false, "failed READ in FPA_BEFORE block for %s", file->name);
+                goto failure;
+            }
+            if (!pmFPAfileCreate(file, view)) {
+                psError(PS_ERR_IO, false, "failed CREATE in FPA_BEFORE block for %s", file->name);
+                goto failure;
+            }
+            break;
+        case PM_FPA_AFTER:
+            if (!pmFPAfileWrite (file, view, config)) {
+                psError(PS_ERR_IO, false, "failed WRITE in FPA_AFTER block for %s", file->name);
+                goto failure;
+            }
+            if (!pmFPAfileClose(file, view)) {
+                psError(PS_ERR_IO, false, "failed CLOSE in FPA_AFTER block for %s", file->name);
+                goto failure;
+            }
+            break;
+        default:
+            psAbort(PS_FILE_LINE, "You can't get here");
+        }
+    }
+
+    // attempt to free data that is no longer needed
+    psMetadataIteratorSet (iter, PS_LIST_HEAD);
+    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+        pmFPAfile *file = item->data.V;
+
+        switch (place) {
+        case PM_FPA_BEFORE:
+            break;
+        case PM_FPA_AFTER:
+            if (!pmFPAfileFreeData(file, view)) {
+                psError(PS_ERR_IO, false, "failed FREE in FPA_AFTER block for %s", file->name);
+                goto failure;
+            }
+            break;
+        default:
+            psAbort(PS_FILE_LINE, "You can't get here");
+        }
+    }
+    psFree (iter);
+    return true;
+
+failure:
+    psFree (iter);
+    return false;
+}
+
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAfileIO.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAfileIO.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAfileIO.h	(revision 9533)
@@ -0,0 +1,42 @@
+/** @file  pmFPAview.h
+*
+*  @brief Tools to manipulate the FPA structure elements.
+*
+*  @ingroup AstroImage
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-06-17 01:50:43 $
+*
+*  Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+*/
+
+#ifndef PM_FPA_FILE_IO_H
+#define PM_FPA_FILE_IO_H
+
+// open the real file corresponding to the given pmFPAfile appropriate to the current view
+bool pmFPAfileOpen (pmFPAfile *file, const pmFPAview *view, pmConfig *config);
+
+// read from the real file corresponding to the given pmFPAfile for the current view
+bool pmFPAfileRead (pmFPAfile *file, const pmFPAview *view, pmConfig *config);
+
+bool pmFPAfileCreate (pmFPAfile *file, const pmFPAview *view);
+
+// write to the real file corresponding to the given pmFPAfile for the current view
+bool pmFPAfileWrite (pmFPAfile *file, const pmFPAview *view, pmConfig *config);
+
+// close the real file corresponding to the given pmFPAfile appropriate to the current view
+bool pmFPAfileClose (pmFPAfile *file, const pmFPAview *view);
+
+// free the data at this level
+bool pmFPAfileFreeData(pmFPAfile *file, const pmFPAview *view);
+
+// set the state of the specified pmFPAfile to active (state == true) or inactive
+// if name is NULL, set the state for all pmFPAfiles
+bool pmFPAfileActivate (psMetadata *files, bool state, char *name);
+
+// examine all pmFPAfiles listed in the files and perform the needed I/O operations (open,read,write,close)
+bool pmFPAfileIOChecks (pmConfig *config, const pmFPAview *view, pmFPAfilePlace place);
+
+# endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAview.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAview.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAview.c	(revision 9533)
@@ -0,0 +1,300 @@
+/** @file  pmFPAview.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-10-06 21:45:58 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include "pslib.h"
+#include "pmHDU.h"
+#include "pmHDUUtils.h"
+#include "pmFPA.h"
+#include "pmFPAview.h"
+
+static void pmFPAviewFree(pmFPAview *view)
+{
+    // No reason to keep this function, apart from the fact that it allows us to type the memBlock
+    return;
+}
+
+pmFPAview *pmFPAviewAlloc(int nRows)
+{
+    pmFPAview *view = psAlloc(sizeof(pmFPAview));
+    psMemSetDeallocator(view, (psFreeFunc) pmFPAviewFree);
+
+    view->nRows = nRows;
+    pmFPAviewReset(view);
+    return view;
+}
+
+
+bool pmFPAviewReset(pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(view, false);
+
+    view->chip    = -1;
+    view->cell    = -1;
+    view->readout = -1;
+    view->iRows   =  0;
+    return true;
+}
+
+
+pmFPALevel pmFPAviewLevel(const pmFPAview *view)
+{
+    PS_ASSERT_PTR_NON_NULL(view, PM_FPA_LEVEL_NONE);
+
+    if (view->chip < 0) {
+        return PM_FPA_LEVEL_FPA;
+    }
+    if (view->cell < 0) {
+        return PM_FPA_LEVEL_CHIP;
+    }
+    if (view->readout < 0) {
+        return PM_FPA_LEVEL_CELL;
+    }
+    return PM_FPA_LEVEL_READOUT;
+}
+
+pmChip *pmFPAviewThisChip(const pmFPAview *view, pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa->chips, NULL);
+
+    if (view->chip < 0) {
+        return NULL;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return NULL;
+    }
+
+    pmChip *chip = fpa->chips->data[view->chip];
+    return chip;
+}
+
+pmChip *pmFPAviewNextChip(pmFPAview *view, pmFPA *fpa, int nStep)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    view->cell = -1;
+    view->readout = -1;
+    view->iRows = 0;
+
+    // if there are no available chips, return NULL
+    if (fpa->chips->n <= 0) {
+        view->chip = -1;
+        return NULL;
+    }
+
+    // clean up < -1 values
+    if (view->chip < -1) {
+        view->chip = -1;
+    }
+
+    // increment to the next chip
+    view->chip += nStep;
+
+    // if we are at the end of the stack, return NULL
+    if (view->chip >= fpa->chips->n) {
+        view->chip = -1;
+        return NULL;
+    }
+
+    // get the correct chip pointer
+    pmChip *chip = fpa->chips->data[view->chip];
+    return (chip);
+}
+
+pmCell *pmFPAviewThisCell(const pmFPAview *view, pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    if (view->cell < 0) {
+        return NULL;
+    }
+
+    pmChip *chip = pmFPAviewThisChip (view, fpa);
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    if (view->cell >= chip->cells->n) {
+        return NULL;
+    }
+
+    pmCell *cell = chip->cells->data[view->cell];
+    return cell;
+}
+
+pmCell *pmFPAviewNextCell (pmFPAview *view, pmFPA *fpa, int nStep)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    pmChip *chip = pmFPAviewThisChip (view, fpa);
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    view->readout = -1;
+    view->iRows = 0;
+
+    // if there are no available cells, return NULL
+    if (chip->cells->n <= 0) {
+        view->cell = -1;
+        return NULL;
+    }
+
+    // clean up < -1 values
+    if (view->cell < -1) {
+        view->cell = -1;
+    }
+
+    // increment to the next cell
+    view->cell += nStep;
+
+    // if we are at the end of the stack, return NULL
+    if (view->cell >= chip->cells->n) {
+        view->cell = -1;
+        return NULL;
+    }
+
+    // get the correct cell pointer
+    pmCell *cell = chip->cells->data[view->cell];
+    return (cell);
+}
+
+pmReadout *pmFPAviewThisReadout (const pmFPAview *view, pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    if (view->readout < 0) {
+        return NULL;
+    }
+
+    pmCell *cell = pmFPAviewThisCell (view, fpa);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+
+    if (view->readout >= cell->readouts->n) {
+        return NULL;
+    }
+
+    pmReadout *readout = cell->readouts->data[view->readout];
+    return readout;
+}
+
+pmReadout *pmFPAviewNextReadout (pmFPAview *view, pmFPA *fpa, int nStep)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    pmCell *cell = pmFPAviewThisCell (view, fpa);
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+
+    view->iRows = 0;
+
+    // if there are no available cells, return NULL
+    if (cell->readouts->n <= 0) {
+        view->readout = -1;
+        return NULL;
+    }
+
+    // clean up < -1 values
+    if (view->readout < -1) {
+        view->readout = -1;
+    }
+
+    // increment to the next cell
+    view->readout += nStep;
+
+    // if we are at the end of the stack, return NULL
+    if (view->readout >= cell->readouts->n) {
+        view->readout = -1;
+        return NULL;
+    }
+
+    // get the correct cell pointer
+    pmReadout *readout = cell->readouts->data[view->readout];
+    return (readout);
+}
+
+pmHDU *pmFPAviewThisHDU(const pmFPAview *view, pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    // the HDU is attached to a cell, chip or fpa
+    // if this view has a -1 for the level which contains the hdu,
+    // there is no unambiguous HDU
+
+    if (view->chip < 0) {
+        return pmHDUFromFPA (fpa);
+    }
+    if (view->cell < 0) {
+        return pmHDUFromChip (pmFPAviewThisChip (view, fpa));
+    }
+    if (view->readout < 0) {
+        return pmHDUFromCell (pmFPAviewThisCell (view, fpa));
+    }
+    return pmHDUFromReadout (pmFPAviewThisReadout (view, fpa));
+}
+
+pmHDU *pmFPAviewThisPHU(const pmFPAview *view, pmFPA *fpa)
+{
+    PS_ASSERT_PTR_NON_NULL(view, NULL);
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+
+    // select the HDU which corresponds to the PHU containing this view
+
+    pmHDU *hdu;
+    pmFPAview new;
+    pmChip *chip;
+    pmCell *cell;
+
+    new = *view;
+
+    if (view->chip < 0) {
+        hdu = pmHDUFromFPA (fpa);
+        if (!hdu)
+            return NULL;
+        if (hdu->blankPHU)
+            return hdu;
+        return NULL;
+    }
+    if (view->cell < 0) {
+        chip = pmFPAviewThisChip (view, fpa);
+        hdu  = pmHDUFromChip (chip);
+        if (!hdu)
+            return NULL;
+        if (hdu->blankPHU)
+            return hdu;
+        new.chip = -1;
+        hdu = pmFPAviewThisPHU (&new, fpa);
+        return hdu;
+    }
+    if (view->readout < 0) {
+        cell = pmFPAviewThisCell (view, fpa);
+        hdu  = pmHDUFromCell (cell);
+        if (!hdu) {
+            psAbort("psModules", "a split readout is not covered by the current paradigm");
+        }
+        if (hdu->blankPHU)
+            return hdu;
+        new.cell = -1;
+        hdu = pmFPAviewThisPHU (&new, fpa);
+        return hdu;
+    }
+    return NULL;
+}
Index: /tags/RHL_v1_1/psModules/src/camera/pmFPAview.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmFPAview.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmFPAview.h	(revision 9533)
@@ -0,0 +1,64 @@
+/** @file  pmFPAview.h
+*
+*  @brief Tools to manipulate the FPA structure elements.
+*
+*  @ingroup AstroImage
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-08-02 02:17:11 $
+*
+*  Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+*/
+
+#ifndef PM_FPA_VIEW_H
+#define PM_FPA_VIEW_H
+
+#include "pmFPA.h"
+
+/// @addtogroup AstroImage
+/// @{
+
+typedef struct
+{
+    int chip;                           // Number of the chip, or -1 for all
+    int cell;                           // Number of the cell, or -1 for all
+    int readout;                        // Number of the readout, or -1 for all
+    int nRows;                          // Maximum number of rows per readout segment read, or 0 for all
+    int iRows;                          // Starting point for this read
+}
+pmFPAview;
+
+// allocate a pmFPAview structure for this fpa and camera
+pmFPAview *pmFPAviewAlloc (int nRows);
+
+// Reset a view
+bool pmFPAviewReset(pmFPAview *view);
+
+// determine the current view level
+pmFPALevel pmFPAviewLevel (const pmFPAview *view);
+
+// return the currently selected chip for this view
+pmChip *pmFPAviewThisChip (const pmFPAview *view, pmFPA *fpa);
+
+// advance view to the next chip
+pmChip *pmFPAviewNextChip (pmFPAview *view, pmFPA *fpa, int nStep);
+
+// return the currently selected cell for this view
+pmCell *pmFPAviewThisCell (const pmFPAview *view, pmFPA *fpa);
+
+// advance view to the next cell
+pmCell *pmFPAviewNextCell (pmFPAview *view, pmFPA *fpa, int nStep);
+
+// return the currently selected readout for this view
+pmReadout *pmFPAviewThisReadout (const pmFPAview *view, pmFPA *fpa);
+
+// advance view to the next readout
+pmReadout *pmFPAviewNextReadout (pmFPAview *view, pmFPA *fpa, int nStep);
+
+// return the HDU corresponding to the current view
+pmHDU *pmFPAviewThisHDU (const pmFPAview *view, pmFPA *fpa);
+pmHDU *pmFPAviewThisPHU (const pmFPAview *view, pmFPA *fpa);
+
+# endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmHDU.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmHDU.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmHDU.c	(revision 9533)
@@ -0,0 +1,215 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#include <pslib.h>
+#include "pmFPA.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static (private) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Move to the appropriate extension in FITS file for HDU
+static bool hduMove(pmHDU *hdu,         // HDU with extname
+                    psFits *fits        // FITS file in which to move
+                   )
+{
+    // Deal with the PHU case
+    if (hdu->blankPHU || !hdu->extname) {
+        if (!psFitsMoveExtNum(fits, 0, false)) {
+            psError(PS_ERR_IO, false, "Unable to move to primary header!\n");
+            return false;
+        }
+        return true;
+    }
+
+    if (!psFitsMoveExtName(fits, hdu->extname)) {
+        psError(PS_ERR_IO, false, "Unable to move to extension %s\n", hdu->extname);
+        return false;
+    }
+
+    return true;
+}
+
+static void hduFree(pmHDU *hdu)
+{
+    psFree(hdu->extname);
+    psFree(hdu->format);
+    psFree(hdu->header);
+    psFree(hdu->images);
+    psFree(hdu->table);
+    psFree(hdu->weights);
+    psFree(hdu->masks);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+pmHDU *pmHDUAlloc(const char *extname   // Extension name
+                 )
+{
+    pmHDU *hdu = psAlloc(sizeof(pmHDU));
+    psMemSetDeallocator(hdu, (psFreeFunc)hduFree);
+
+    if (!extname || strlen(extname) == 0) {
+        hdu->blankPHU = true;
+        hdu->extname = NULL;
+    } else {
+        hdu->blankPHU = false;
+        hdu->extname = psStringCopy(extname);
+    }
+    hdu->format  = NULL;
+    hdu->header  = NULL;
+    hdu->images  = NULL;
+    hdu->table   = NULL;
+    hdu->weights = NULL;
+    hdu->masks   = NULL;
+
+    return hdu;
+}
+
+bool pmHDUReadHeader(pmHDU *hdu,        // HDU for which to read header
+                     psFits *fits       // FITS file from which to read
+                    )
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    // Move to the appropriate extension
+    psTrace("psModules.camera", 5, "Moving to extension %s...\n", hdu->extname);
+    if (!hduMove(hdu, fits)) {
+        return false;
+    }
+
+    if (!hdu->header) {
+        psTrace("psModules.camera", 5, "Reading the header...\n");
+        hdu->header = psFitsReadHeader(NULL, fits);
+        if (! hdu->header) {
+            psError(PS_ERR_IO, false, "Unable to read header for extension %s\n", hdu->extname);
+            return false;
+        }
+    }
+
+    return true;
+}
+
+// Read the HDU
+// XXX: Add a region specifier?
+bool pmHDURead(pmHDU *hdu,              // HDU to read
+               psFits *fits             // FITS file to read from
+              )
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    // Read the header; includes the move
+    if (!pmHDUReadHeader(hdu, fits)) {
+        return false;
+    }
+
+    if (hdu->blankPHU) {
+        // Done already!
+        return true;
+    }
+
+    #ifdef FITS_TABLES
+    // What type is it?
+    if (psFitsIsImage(hdu->header)) {
+        #endif
+        if (hdu->images) {
+            psLogMsg(__func__, PS_LOG_WARN, "HDU %s has already been read --- overwriting.\n", hdu->extname);
+            psFree(hdu->images);        // Blow away anything existing
+        }
+        psTrace("psModules.camera", 5, "Reading the pixels...\n");
+        hdu->images = psFitsReadImageCube(fits, psRegionSet(0,0,0,0));
+        if (! hdu->images) {
+            psError(PS_ERR_IO, false, "Unable to read pixels for extension %s\n", hdu->extname);
+            return false;
+        }
+        return true;
+        #ifdef FITS_TABLES
+
+    }
+    if (psFitsIsTable(hdu->table)) {
+        // Read the table
+        if (hdu->table) {
+            psFree(hdu->table);         // Blow away anything existing
+        }
+        hdu->table = psFitsReadTable(fits);
+        return true;
+    }
+
+    psError(PS_ERR_UNKNOWN, true, "No idea what this HDU consists of!\n");
+    return false;
+    #endif
+}
+
+// Write the HDU
+// XXX: Add a region specifier?
+bool pmHDUWrite(pmHDU *hdu,             // HDU to write
+                psFits *fits            // FITS file to write to
+               )
+{
+    PS_ASSERT_PTR_NON_NULL(hdu, false);
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+
+    psTrace("psModules.camera", 7, "Writing HDU %s\n", hdu->extname);
+
+    if (hdu->images && hdu->table && !hdu->header) {
+        psError(PS_ERR_IO, true, "Both image and table data provided in HDU, but no header --- "
+                "don't know which to write!\n");
+        return false;
+    }
+
+    if (!hdu->images && !hdu->table && !hdu->header) {
+        psLogMsg(__func__, PS_LOG_WARN, "Nothing to write for HDU %s\n", hdu->extname);
+        return false;
+    }
+
+    // Preserve the extension name, if it's the PHU
+    char *extname = hdu->extname;       // The name of the extension
+    if (!extname && hdu->header) {
+        bool mdok = true;               // Status of MD lookup
+        extname = psMetadataLookupStr(&mdok, hdu->header, "EXTNAME");
+        if (!mdok || !extname || strlen(extname) == 0) {
+            extname = "";
+        }
+    }
+
+    // Only a header
+    if (!hdu->images && !hdu->table && !psFitsWriteBlank(fits, hdu->header, extname)) {
+        psError(PS_ERR_IO, false, "Unable to write header for extension %s\n", extname);
+    }
+
+    #ifdef FITS_TABLES
+    if (hdu->images && (!hdu->table || psFitsIsImage(hdu->header))) {
+        psFitsWriteImageCube(fits, hdu->header, hdu->images, extname);
+        return true;
+    }
+
+    if (hdu->table && (!hdu->images || psFitsIsTable(hdu->header))) {
+        bool psFitsWriteTable(psFits* fits, const psMetadata *header, const psArray* table);
+        return true;
+    }
+
+    psError(PS_ERR_IO, true, "No idea what this HDU consists of!\n");
+    return false;
+    #else
+
+    if (hdu->images) {
+        psTrace("psModules.camera", 9, "Writing pixels for %s\n", hdu->extname);
+        if (!psFitsWriteImageCube(fits, hdu->header, hdu->images, extname)) {
+            psError(PS_ERR_IO, false, "Unable to write image to extension %s\n", hdu->extname);
+            return false;
+        }
+    }
+    return true;
+    #endif
+}
+
Index: /tags/RHL_v1_1/psModules/src/camera/pmHDU.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmHDU.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmHDU.h	(revision 9533)
@@ -0,0 +1,37 @@
+#ifndef PM_HDU_H
+#define PM_HDU_H
+
+// An instance of the FITS Header Data Unit
+typedef struct
+{
+    psString extname;                   // The extension name
+    bool blankPHU;                      // Is this a blank FITS Primary Header Unit, i.e., no data?
+    psMetadata *format;                 // The camera format
+    psMetadata *header;                 // The FITS header, or NULL if primary for FITS; or section info
+    psArray *images;                    // The pixel data
+    psArray *weights;                   // The pixel data
+    psArray *masks;                     // The pixel data
+    psArray *table;                     // The table data
+}
+pmHDU;
+
+
+// Allocators
+pmHDU *pmHDUAlloc(const char *extname);
+
+// Read the header
+bool pmHDUReadHeader(pmHDU *hdu,        // HDU for which to read header
+                     psFits *fits       // FITS file from which to read
+                    );
+
+// Read the HDU
+bool pmHDURead(pmHDU *hdu,              // HDU to read
+               psFits *fits             // FITS file to read from
+              );
+
+// Write the HDU
+bool pmHDUWrite(pmHDU *hdu,             // HDU to write
+                psFits *fits            // FITS file to write to
+               );
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmHDUGenerate.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmHDUGenerate.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmHDUGenerate.c	(revision 9533)
@@ -0,0 +1,594 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmFPA.h"
+#include "pmHDU.h"
+#include "pmHDUUtils.h"
+
+#include "pmHDUGenerate.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Add cells in a chip to a list
+static bool addCellsFromChip(psList *list, // List of cells
+                             const pmChip *chip // The chip from which to add cells
+                            )
+{
+    assert(list);
+    assert(chip);
+
+    psArray *cells = chip->cells;       // Array of cells
+    bool result = true;                 // Result of adding cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // A cell
+        if (!cell->hdu) {               // Don't add cells that have their own HDU
+            result |= psListAdd(list, PS_LIST_TAIL, cell);
+        }
+    }
+
+    return result;
+}
+
+// Add cells in an FPA to a list
+static bool addCellsFromFPA(psList *list, // List of cells
+                            const pmFPA *fpa // The FPA from which to add cells
+                           )
+{
+    assert(list);
+    assert(fpa);
+
+    psArray *chips = fpa->chips;        // Array of chips
+    bool result = true;                 // Result of adding cells
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // A chip
+        if (! chip->hdu) {              // Don't add chips that have their own HDU
+            result |= addCellsFromChip(list, chip);
+        }
+    }
+
+    return result;
+}
+
+// Get the maximum extent of the HDU from the trimsec and biassecs
+static bool sizeHDU(int *xSize, int *ySize, // Size of HDU
+                    psList *cells       // List of cells
+                   )
+{
+    psListIterator *cellsIter = psListIteratorAlloc(cells, PS_LIST_HEAD, false); // Iterator for cells
+    pmCell *cell = NULL;                // The cell from iteration
+    bool mdok = true;                   // Status of MD lookup
+    *xSize = 0;
+    *ySize = 0;
+    while ((cell = psListGetAndIncrement(cellsIter))) {
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (mdok && trimsec && !psRegionIsNaN(*trimsec)) {
+            *xSize = PS_MAX(trimsec->x1, *xSize);
+            *ySize = PS_MAX(trimsec->y1, *ySize);
+        } else {
+            psFree(cellsIter);
+            return false;
+        }
+        psList *biassecs = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.BIASSEC"); // Bias sections
+        if (mdok && biassecs) {
+            psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+            psRegion *biassec = NULL;   // The bias section
+            while ((biassec = psListGetAndIncrement(biassecsIter))) {
+                if (!psRegionIsNaN(*biassec)) {
+                    *xSize = PS_MAX(biassec->x1, *xSize);
+                    *ySize = PS_MAX(biassec->y1, *ySize);
+                } else {
+                    psFree(biassecsIter);
+                    psFree(cellsIter);
+                    return false;
+                }
+            }
+            psFree(biassecsIter);
+        }
+    }
+    psFree(cellsIter);
+
+    return (*xSize != 0 && *ySize != 0);
+}
+
+
+static psRegion *sectionForImage(int *position, // Position on the output image, updated
+                                 const psImage *image, // Image containing the sizes and offsets
+                                 int readdir // Read direction, 1=rows, 2=cols
+                                )
+{
+    psRegion *region = NULL;            // The region to return
+    switch (readdir) {
+    case 1:                           // Read direction is rows
+        region = psRegionAlloc(*position, *position + image->numCols, image->row0,
+                               image->row0 + image->numRows);
+        *position += image->numCols;
+        break;
+    case 2:                           // Read direction is columns
+        region = psRegionAlloc(image->col0, image->col0 + image->numCols, *position,
+                               *position + image->numRows);
+        *position += image->numRows;
+        break;
+    default:
+        psAbort(__func__, "Shouldn't ever get here!\n");
+    }
+
+    return region;
+}
+
+static bool doBiasSections(int *position, // Position on the output image, updated
+                           int *readdir,// Read direction for cells
+                           pmCell *cell // Cell
+                          )
+{
+    psMetadataItem *biassecItem = psMetadataLookup(cell->concepts, "CELL.BIASSEC"); // Bias sections
+    if (!biassecItem) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.BIASSEC has not been initialised in cell --- "
+                 "ignored.\n");
+        return false;
+    }
+    psFree(biassecItem->data.V);        // Blow away the old list
+    psList *biassecs = psListAlloc(NULL);
+    biassecItem->data.V = biassecs;
+
+    bool mdok = true;                   // Status of MD lookup
+    int cellreaddir = psMetadataLookupS32(&mdok, cell->concepts, "CELL.READDIR"); // Read direction
+    if (!mdok || (cellreaddir != 1 && cellreaddir != 2)) {
+        // Probably unnecessary, but just in case....
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.READDIR is not set in cell --- ignored.\n");
+        return false;
+    }
+    if (*readdir == 0) {
+        *readdir = cellreaddir;
+    } else if (*readdir != cellreaddir) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.READDIR does not match read direction for HDU --- ignored.\n");
+        return false;
+    }
+
+    pmReadout *readout = cell->readouts->data[0]; // The first readout, as representative
+    psList *biases = readout->bias; // The bias images from the readout
+
+    psListIterator *biasIter = psListIteratorAlloc(biases, PS_LIST_HEAD, true); // Iterator for biases
+    psImage *bias = NULL;       // Bias image from iteration
+    while ((bias = psListGetAndIncrement(biasIter))) {
+        // Construct a region
+        psRegion *biassec = sectionForImage(position, bias, *readdir);
+        psListAdd(biassecs, PS_LIST_TAIL, biassec);
+        psFree(biassec);        // Drop reference
+    }
+    psFree(biasIter);
+
+    return true;
+}
+
+// Generate CELL.TRIMSEC and CELL.BIASSEC for the cells
+static bool generateTrimBias(psList *cells // List of cells below the HDU
+                            )
+{
+    pmCell *cell = NULL;                // Cell from iteration
+    int numCells = cells->n;            // Number of cells
+    int cellNum = 0;                    // The cell number
+    int position = 0;                   // Position on the image
+    bool mdok = true;                   // Status of MD lookup
+    int readdir = 0;                    // Read direction (1=rows, 2=cols)
+
+    // First run through to do the LHS biases
+    psListIterator *cellsIter = psListIteratorAlloc(cells, PS_LIST_HEAD, false); // Iterator for cells
+    bool done = false;                  // Done with iteration (due to being halfway through)?
+    while ((cell = psListGetAndIncrement(cellsIter)) && !done) {
+        if (cellNum <= numCells/2 - 1) {
+            doBiasSections(&position, &readdir, cell);
+            cellNum++;
+        } else {
+            done = true;
+        }
+    }
+
+    // Second run through to do the trim sections
+    psListIteratorSet(cellsIter, PS_LIST_HEAD);
+    while ((cell = psListGetAndIncrement(cellsIter))) {
+        psMetadataItem *trimsecItem = psMetadataLookup(cell->concepts, "CELL.TRIMSEC"); // Item with trimsec
+        if (!trimsecItem || trimsecItem->type != PS_DATA_REGION) {
+            psLogMsg(__func__, PS_LOG_WARN, "CELL.TRIMSEC has not been initialised in cell --- "
+                     "ignored.\n");
+            continue;
+        }
+        psRegion *trimsec = trimsecItem->data.V; // Trim section
+
+        int cellreaddir = psMetadataLookupS32(&mdok, cell->concepts, "CELL.READDIR"); // Read direction
+        if (!mdok || (cellreaddir != 1 && cellreaddir != 2)) {
+            // Probably unnecessary, but just in case....
+            psLogMsg(__func__, PS_LOG_WARN, "CELL.READDIR is not set in cell --- ignored.\n");
+            continue;
+        }
+        if (readdir == 0 && mdok && cellreaddir != 0) {
+            readdir = cellreaddir;
+        } else if (readdir != cellreaddir) {
+            psLogMsg(__func__, PS_LOG_WARN, "CELL.READDIR for cells within the HDU do not match!\n");
+            cellreaddir = readdir;
+        }
+
+        pmReadout *readout = cell->readouts->data[0]; // The first readout, as representative
+        psImage *image = readout->image;// The proper image
+        if (!image) {
+            continue;
+        }
+        // New reference
+        trimsec = sectionForImage(&position, image, cellreaddir);
+        psFree(trimsecItem->data.V);
+        trimsecItem->data.V = trimsec;
+    }
+
+    // A final run through to do the RHS biases
+    psListIteratorSet(cellsIter, cellNum);
+    while ((cell = psListGetAndIncrement(cellsIter))) {
+        doBiasSections(&position, &readdir, cell);
+    }
+
+    // Clean up
+    psFree(cellsIter);
+
+    return (position > 0);
+}
+
+// Generate the HDU, given a list of cells below that HDU.  This is the main engine function, that does all
+// the work.
+static bool generateHDU(pmHDU *hdu,     // HDU to generate
+                        psList *cells   // List of cells
+                       )
+{
+    // Check the number of readouts is consistent within the HDU
+    int numReadouts = -1;               // Number of readouts
+    psElemType type = 0;                // Type of readout images
+    {
+        psListIterator *iter = psListIteratorAlloc(cells, PS_LIST_HEAD, false); // Iterator for cells
+        pmCell *cell = NULL;                // The cell from iteration
+        while ((cell = psListGetAndIncrement(iter)))
+        {
+            psArray *readouts = cell->readouts;
+            if (numReadouts == -1) {
+                numReadouts = readouts->n;
+            } else if (readouts->n != numReadouts) {
+                psError(PS_ERR_IO, true, "Number of readouts doesn't match: %ld vs %d\n", readouts->n,
+                        numReadouts);
+                return false;
+            }
+            for (int i = 0; i < numReadouts; i++) {
+                pmReadout *readout = readouts->data[i]; // The readout
+                if (!readout || !readout->image) {
+                    continue;
+                }
+                psElemType imageType = readout->image->type.type; // Type for this image
+                if (type == 0) {
+                    type = imageType;
+                } else if (type != imageType) {
+                    psLogMsg(__func__, PS_LOG_WARN, "Images within the HDU are of different types "
+                             "(%x vs %x) --- promoting\n", type, imageType);
+                    type = PS_MAX(type, imageType);
+                }
+            }
+        }
+        psFree(iter);
+    }
+    if (numReadouts == 0 || type == 0) {
+        // Nothing from which to create an HDU
+        psFree(cells);
+        return false;
+    }
+
+    // Get the size of the HDU, either from existing trimsec and biassec, or generate these and try again
+    int xSize = 0, ySize = 0;           // Size of HDU
+    if (!sizeHDU(&xSize, &ySize, cells) && !(generateTrimBias(cells) && sizeHDU(&xSize, &ySize, cells))) {
+        psError(PS_ERR_IO, true, "Unable to determine size of HDU!\n");
+        return false;
+    }
+
+    // Generate the HDU
+    hdu->images = psArrayAlloc(numReadouts);
+    hdu->images->n = numReadouts;
+    for (int i = 0; i < numReadouts; i++) {
+        psImage *image = psImageAlloc(xSize, ySize, type);
+        psImageInit(image, 0.0);
+        hdu->images->data[i] = image;
+    }
+
+    // Insert the pixels into the HDU
+    {
+        psListIterator *iter = psListIteratorAlloc(cells, PS_LIST_HEAD, false); // Iterator for cells
+        pmCell *cell = NULL;           // The cell from iteration
+        bool mdok = true;               // Result of MD lookup
+        while ((cell = psListGetAndIncrement(iter)))
+        {
+            psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+            if (!mdok || !trimsec) {
+                psAbort(__func__, "Shouldn't ever get here --- CELL.TRIMSEC should have been set above.\n");
+            }
+            psList *biassecs = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.BIASSEC"); // Bias secionts
+            if (!mdok || !biassecs) {
+                psAbort(__func__, "Shouldn't ever get here --- CELL.BIASSEC should have been set above.\n");
+            }
+            psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+
+            psArray *readouts = cell->readouts; // Array of readouts
+            psArray *hduImages = hdu->images; // Array of images in the HDU
+            for (int i = 0; i < readouts->n; i++) {
+                pmReadout *readout = readouts->data[i]; // The readout of interest
+                if (!readout) {
+                    continue;
+                }
+                psImage *image = readout->image; // The image pixels
+                psImage *hduImage = hduImages->data[i]; // The HDU image of interest
+
+                if (image->numCols != trimsec->x1 - trimsec->x0 ||
+                        image->numRows != trimsec->y1 - trimsec->y0) {
+                    psString trimsecString = psRegionToString(*trimsec);
+                    psLogMsg(__func__, PS_LOG_WARN, "Image size (%dx%d) does not match CELL.TRIMSEC (%s).\n",
+                             image->numCols, image->numRows, trimsecString);
+                    psFree(trimsecString);
+                }
+                psImageOverlaySection(hduImage, image, trimsec->x0, trimsec->y0, "=");
+
+                // Reference the HDU version, so that subsequent changes will touch the HDU
+                psFree(image);
+                readout->image = psImageSubset(hduImage, *trimsec);
+
+                if (biassecs->n != readout->bias->n) {
+                    psLogMsg(__func__, PS_LOG_WARN, "Number of bias sections (%ld) and number of biases (%ld)"
+                             " do not match.\n", biassecs->n, readout->bias->n);
+                }
+                psListIterator *biasIter = psListIteratorAlloc(readout->bias, PS_LIST_HEAD, false); // Iteratr
+                psImage *bias = NULL;   // Bias image, from iteration
+                psListIteratorSet(biassecsIter, PS_LIST_HEAD);
+                psRegion *biassec = NULL; // Bias region, from iteration
+                psList *newBias = psListAlloc(NULL); // New list of bias images
+                while ((bias = psListGetAndIncrement(biasIter)) &&
+                        (biassec = psListGetAndIncrement(biassecsIter))) {
+                    if (bias->numCols != biassec->x1 - biassec->x0 ||
+                            bias->numRows != biassec->y1 - biassec->y0) {
+                        psString biassecString = psRegionToString(*biassec);
+                        psLogMsg(__func__, PS_LOG_WARN, "Bias size (%dx%d) does not match CELL.BIASSEC (%s)."
+                                 "\n", bias->numCols, bias->numRows, biassecString);
+                        psFree(biassecString);
+                    }
+
+                    psImageOverlaySection(hduImage, bias, biassec->x0, biassec->y0, "=");
+
+                    // Reference the HDU version, so that subsequent changes will touch the HDU
+                    bias = psImageSubset(hduImage, *biassec);
+                    psListAdd(newBias, PS_LIST_TAIL, bias);
+                }
+                psFree(biasIter);
+
+                // Add on the new list of bias images
+                psFree(readout->bias);
+                readout->bias = newBias;
+            }
+            psFree(biassecsIter);
+        } // Iterating over cells within the HDU
+        psFree(iter);
+    }
+    psFree(cells);
+
+    return true;
+}
+
+// Return the level that an extension applies to
+static pmFPALevel extensionLevel(pmHDU *hdu // HDU to check
+                                )
+{
+    if (!hdu->format) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "HDU does not have a camera format.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *file = psMetadataLookupMD(&mdok, hdu->format, "FILE"); // File information for camera format
+    if (!mdok || !file) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Can't file FILE information for camera format "
+                "configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+    psString extensions = psMetadataLookupStr(&mdok, file, "EXTENSIONS"); // Where the HDUs are
+    if (!mdok || !extensions || strlen(extensions) == 0) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Can't find EXTENSIONS in the FILE information of the camera "
+                "format configuration.\n");
+        return PM_FPA_LEVEL_NONE;
+    }
+    if (strcasecmp(extensions, "CELL") == 0) {
+        return PM_FPA_LEVEL_CELL;
+    }
+    if (strcasecmp(extensions, "CHIP") == 0) {
+        return PM_FPA_LEVEL_CHIP;
+    }
+    if (strcasecmp(extensions, "FPA") == 0) {
+        return PM_FPA_LEVEL_FPA;
+    }
+    if (strcasecmp(extensions, "NONE") == 0) {
+        return PM_FPA_LEVEL_NONE;
+    }
+    // No idea what it is
+    psError(PS_ERR_IO, true, "EXTENSIONS (%s) in FILE information is not FPA, CHIP, CELL or NONE.\n",
+            extensions);
+    return PM_FPA_LEVEL_NONE;
+}
+
+// Generate HDU for cells belonging to a chip --- just an iterator
+static bool generateForCells(pmChip *chip // Chip for which to generate HDUs
+                            )
+{
+    psArray *cells = chip->cells;       // Array of cells
+    bool status = true;                 // Status of HDU generation
+    for (long i = 0; i < cells->n; i++) {
+        status |= pmHDUGenerateForCell(cells->data[i]);
+    }
+    return status;
+}
+
+// Generate HDU for chips belonging to an FPA --- just an iterator
+static bool generateForChips(pmFPA *fpa // FPA for which to generate HDUs
+                            )
+{
+    psArray *chips = fpa->chips;        // Array of chips
+    bool status = true;                 // Status of HDU generation
+    for (long i = 0; i < chips->n; i++) {
+        status |= pmHDUGenerateForChip(chips->data[i]);
+    }
+    return status;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Generate an HDU from a cell with pixels
+bool pmHDUGenerateForCell(pmCell *cell  // The cell
+                         )
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+
+    // Get the HDU and a list of cells below it
+    pmHDU *hdu = pmHDUFromCell(cell); // The HDU in the cell
+    if (!hdu) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Can't find an HDU for cell.\n");
+        return false;
+    }
+    if (hdu->images) {
+        // It's already here!
+        return true;
+    }
+
+    pmFPALevel extLevel = extensionLevel(hdu);
+    switch (extLevel) {
+    case PM_FPA_LEVEL_NONE:
+    case PM_FPA_LEVEL_CELL: {
+            psList *cells = psListAlloc(NULL); // List of cells below the HDU
+
+            if (cell->hdu) {
+                psListAdd(cells, PS_LIST_TAIL, cell);
+            } else {
+                pmChip *chip = cell->parent;    // The parent chip
+                if (chip->hdu) {
+                    addCellsFromChip(cells, chip);
+                } else {
+                    pmFPA *fpa = chip->parent;  // The parent FPA
+                    if (fpa->hdu) {
+                        addCellsFromFPA(cells, fpa);
+                    }
+                }
+            }
+            if (cells->n == 0) {
+                // Nothing to do
+                return true;
+            }
+
+            return generateHDU(hdu, cells);
+        }
+    case PM_FPA_LEVEL_CHIP:
+    case PM_FPA_LEVEL_FPA:
+        return pmHDUGenerateForChip(cell->parent);
+    default:
+        psAbort(__func__, "Shouldn't ever get here.\n");
+    }
+    return false;
+}
+
+// Generate an HDU from a chip with pixels
+bool pmHDUGenerateForChip(pmChip *chip  // The chip
+                         )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+
+    // Get the HDU and a list of cells below it
+    pmHDU *hdu = pmHDUFromChip(chip);   // The HDU in the chip
+    if (!hdu) {
+        // Nothing here; need to look further down
+        return generateForCells(chip);
+    }
+    if (hdu->images) {
+        // It's already here!
+        return true;
+    }
+
+    pmFPALevel extLevel = extensionLevel(hdu);
+    switch (extLevel) {
+    case PM_FPA_LEVEL_NONE:
+    case PM_FPA_LEVEL_CELL:
+        // Work on lower levels
+        return generateForCells(chip);
+    case PM_FPA_LEVEL_CHIP: {
+            // Work on this level
+            psList *cells = psListAlloc(NULL);  // List of cells below the HDU
+            if (chip->hdu) {
+                addCellsFromChip(cells, chip);
+            } else {
+                pmFPA *fpa = chip->parent;  // The parent FPA
+                if (fpa->hdu) {
+                    addCellsFromFPA(cells, fpa);
+                }
+            }
+            if (cells->n == 0) {
+                // Nothing to do
+                return true;
+            }
+
+            return generateHDU(hdu, cells);
+        }
+    case PM_FPA_LEVEL_FPA:
+        // Work on higher levels
+        return pmHDUGenerateForFPA(chip->parent);
+    default:
+        psAbort(__func__, "Shouldn't ever get here.\n");
+    }
+    return false;
+}
+
+
+// Generate an HDU from an FPA with pixels
+bool pmHDUGenerateForFPA(pmFPA *fpa     // The fpa
+                        )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+
+    // Get the HDU and a list of cells below it
+    pmHDU *hdu = pmHDUFromFPA(fpa);     // The HDU in the FPA
+    if (!hdu) {
+        // Nothing here; need to look further down
+        return generateForChips(fpa);
+    }
+    if (hdu->images) {
+        // It's already here!
+        return true;
+    }
+
+    pmFPALevel extLevel = extensionLevel(hdu);
+    switch (extLevel) {
+    case PM_FPA_LEVEL_NONE:
+    case PM_FPA_LEVEL_CELL:
+    case PM_FPA_LEVEL_CHIP:
+        // Work on lower levels
+        return generateForChips(fpa);
+    case PM_FPA_LEVEL_FPA: {
+            // Work on this level
+            psList *cells = psListAlloc(NULL); // List of cells below the HDU
+            if (fpa->hdu) {
+                addCellsFromFPA(cells, fpa);
+            }
+            if (cells->n == 0) {
+                // Nothing to do
+                return true;
+            }
+
+            return generateHDU(hdu, cells);
+        }
+    default:
+        psAbort(__func__, "Shouldn't ever get here.\n");
+    }
+    return false;
+}
Index: /tags/RHL_v1_1/psModules/src/camera/pmHDUGenerate.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmHDUGenerate.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmHDUGenerate.h	(revision 9533)
@@ -0,0 +1,19 @@
+#ifndef PM_HDU_GENERATE_H
+#define PM_HDU_GENERATE_H
+
+#include "pmFPA.h"
+
+// Generate an HDU for a cell with pixels
+bool pmHDUGenerateForCell(pmCell *cell  // The cell for which to generate an HDU
+                         );
+
+// Generate an HDU for a cell with pixels
+bool pmHDUGenerateForChip(pmChip *chip  // The chip for which to generate an HDU
+                         );
+
+// Generate an HDU from an FPA with pixels
+bool pmHDUGenerateForFPA(pmFPA *fpa     // The fpa for which to generate an HDU
+                        );
+
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/camera/pmHDUUtils.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmHDUUtils.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmHDUUtils.c	(revision 9533)
@@ -0,0 +1,150 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include "pmFPA.h"
+#include "pmHDU.h"
+#include "pmHDUUtils.h"
+
+
+pmHDU *pmHDUFromFPA(const pmFPA *fpa    // FPA for which to find HDU
+                   )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, NULL);
+    return fpa->hdu;
+}
+
+pmHDU *pmHDUFromChip(const pmChip *chip // Chip for which to find HDU
+                    )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, NULL);
+
+    pmHDU *hdu = chip->hdu;             // The HDU information
+    if (!hdu) {
+        hdu = pmHDUFromFPA(chip->parent); // Grab HDU info from the FPA
+    }
+
+    return hdu;
+}
+
+pmHDU *pmHDUFromCell(const pmCell *cell // Cell for which to find HDU
+                    )
+{
+    PS_ASSERT_PTR_NON_NULL(cell, NULL);
+
+    pmHDU *hdu = cell->hdu;             // The HDU information
+    if (!hdu) {
+        hdu = pmHDUFromChip(cell->parent); // Grab HDU info from the chip
+    }
+
+    return hdu;
+}
+
+pmHDU *pmHDUFromReadout(const pmReadout *readout // Readout for which to find HDU
+                       )
+{
+    PS_ASSERT_PTR_NON_NULL(readout, NULL);
+
+    pmCell *cell = readout->parent; // cell containing this readout;
+    pmHDU *hdu = pmHDUFromCell(cell);
+    return hdu;
+}
+
+// Get the lowest HDU
+pmHDU *pmHDUGetLowest(const pmFPA *fpa, // The FPA
+                      const pmChip *chip, // The chip, or NULL
+                      const pmCell *cell // The cell, or NULL
+                     )
+{
+    pmHDU *hdu = NULL;          // The HDU that's at the lowest level
+    if (cell) {
+        hdu = pmHDUFromCell(cell);
+    } else if (chip) {
+        hdu = pmHDUFromChip(chip);
+    } else if (fpa) {
+        hdu = pmHDUFromFPA(fpa);
+    }
+
+    return hdu;
+}
+
+// Get the highest HDU
+pmHDU *pmHDUGetHighest(const pmFPA *fpa, // The FPA
+                       const pmChip *chip, // The chip, or NULL
+                       const pmCell *cell // The cell, or NULL
+                      )
+{
+    pmHDU *hdu = NULL;          // The HDU that's at the highest level
+    if (fpa) {
+        hdu = pmHDUFromFPA(fpa);
+    }
+    if (!hdu && chip) {
+        hdu = pmHDUFromChip(chip);
+    }
+    if (!hdu && cell) {
+        hdu = pmHDUFromCell(cell);
+    }
+
+    return hdu;
+}
+
+void pmHDUPrint(FILE *fd,               // File descriptor to which to print
+                const pmHDU *hdu,       // HDU to print
+                int level,              // Level at which to print
+                bool header             // Print header?
+               )
+{
+    PS_ASSERT_PTR_NON_NULL(hdu,);
+
+    #ifndef PS_NO_TRACE
+
+    if (hdu->blankPHU) {
+        psTrace("psModules.camera", level, "HDU: (PHU)\n");
+    } else {
+        psTrace("psModules.camera", level, "HDU: %s\n", hdu->extname);
+    }
+
+    psTrace("psModules.camera", level + 1, "Format: %p\n", hdu->format);
+    if (header) {
+        if (hdu->header) {
+            psTrace("psModules.camera", level + 1, "Header:\n");
+            psMetadataPrint(fd, hdu->header, level + 2);
+        } else {
+            psTrace("psModules.camera", level + 1, "No header.\n");
+        }
+    }
+
+    if (hdu->images) {
+        psTrace("psModules.camera", level + 1, "Images:\n");
+        for (long i = 0; i < hdu->images->n; i++) {
+            psImage *image = hdu->images->data[i]; // Image of interest
+            psTrace("psModules.camera", level + 2, "%ld: %dx%d\n", i, image->numCols, image->numRows);
+        }
+    } else {
+        psTrace("psModules.camera", level + 1, "NO images.\n");
+    }
+
+    if (hdu->masks) {
+        psTrace("psModules.camera", level + 1, "Masks:\n");
+        for (long i = 0; i < hdu->masks->n; i++) {
+            psImage *mask = hdu->masks->data[i]; // Mask of interest
+            psTrace("psModules.camera", level + 2, "%ld: %dx%d\n", i, mask->numCols, mask->numRows);
+        }
+    } else {
+        psTrace("psModules.camera", level + 1, "NO masks.\n");
+    }
+
+    if (hdu->weights) {
+        psTrace("psModules.camera", level + 1, "Weights:\n");
+        for (long i = 0; i < hdu->masks->n; i++) {
+            psImage *weight = hdu->weights->data[i]; // Weight image of interest
+            psTrace("psModules.camera", level + 2, "%ld: %dx%d\n", i, weight->numCols, weight->numRows);
+        }
+    } else {
+        psTrace("psModules.camera", level + 1, "NO weights.\n");
+    }
+    #endif
+
+    return;
+}
Index: /tags/RHL_v1_1/psModules/src/camera/pmHDUUtils.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/camera/pmHDUUtils.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/camera/pmHDUUtils.h	(revision 9533)
@@ -0,0 +1,36 @@
+#ifndef PM_HDU_UTILS_H
+#define PM_HDU_UTILS_H
+
+#include "pmFPA.h"
+#include "pmHDU.h"
+
+// Get the lowest HDU in the hierarchy, as supplied
+pmHDU *pmHDUGetLowest(const pmFPA *fpa, // The FPA
+                      const pmChip *chip, // The chip, or NULL
+                      const pmCell *cell // The cell, or NULL
+                     );
+
+// Get the highest HDU in the hierarchy, as supplied
+pmHDU *pmHDUGetHighest(const pmFPA *fpa, // The FPA
+                       const pmChip *chip, // The chip, or NULL
+                       const pmCell *cell // The cell, or NULL
+                      );
+
+// Find the HDU in the FPA hierarchy
+pmHDU *pmHDUFromFPA(const pmFPA *fpa    // FPA for which to find HDU
+                   );
+pmHDU *pmHDUFromChip(const pmChip *chip // Chip for which to find HDU
+                    );
+pmHDU *pmHDUFromCell(const pmCell *cell // Cell for which to find HDU
+                    );
+pmHDU *pmHDUFromReadout(const pmReadout *readout // Readout for which to find HDU
+                       );
+
+// Print details about an HDU
+void pmHDUPrint(FILE *fd,               // File descriptor to which to print
+                const pmHDU *hdu,       // HDU to print
+                int level,              // Level at which to print
+                bool header             // Print header?
+               );
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/concepts/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/src/concepts/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/concepts/.cvsignore	(revision 9533)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/RHL_v1_1/psModules/src/concepts/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/src/concepts/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/concepts/Makefile.am	(revision 9533)
@@ -0,0 +1,21 @@
+noinst_LTLIBRARIES = libpsmodulesconcepts.la
+
+libpsmodulesconcepts_la_CPPFLAGS = $(SRCINC) $(PSMODULES_CFLAGS)
+libpsmodulesconcepts_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulesconcepts_la_SOURCES  = \
+	pmConcepts.c \
+	pmConceptsAverage.c \
+	pmConceptsRead.c \
+	pmConceptsWrite.c \
+	pmConceptsStandard.c \
+	pmConceptsPhotcode.c
+
+pkginclude_HEADERS = \
+	pmConcepts.h \
+	pmConceptsAverage.h \
+	pmConceptsRead.h \
+	pmConceptsWrite.h \
+	pmConceptsStandard.h \
+	pmConceptsPhotcode.h
+
+CLEANFILES = *~
Index: /tags/RHL_v1_1/psModules/src/concepts/pmConcepts.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/concepts/pmConcepts.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/concepts/pmConcepts.c	(revision 9533)
@@ -0,0 +1,825 @@
+// XXX *REALLY* need generic "concept update" and "concept read" functions that handles the type transparently
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <pslib.h>
+
+#include "pmConcepts.h"
+#include "pmConceptsRead.h"
+#include "pmConceptsWrite.h"
+#include "pmConceptsStandard.h"
+
+static bool conceptsInitialised = false;// Have concepts been read?
+static psMetadata *conceptsFPA = NULL;  // Known concepts for FPA
+static psMetadata *conceptsChip = NULL; // Known concepts for chip
+static psMetadata *conceptsCell = NULL; // Known concepts for cell
+
+// Free a concept
+static void conceptSpecFree(pmConceptSpec *spec)
+{
+    psFree(spec->blank);
+}
+
+pmConceptSpec *pmConceptSpecAlloc(psMetadataItem *blank, // Blank value; contains the name
+                                  pmConceptParseFunc parse, // Function to call to parse the concept
+                                  pmConceptFormatFunc format // Function to call to format the concept
+                                 )
+{
+    pmConceptSpec *spec = psAlloc(sizeof(pmConceptSpec));
+    psMemSetDeallocator(spec, (psFreeFunc)conceptSpecFree);
+
+    spec->blank = psMemIncrRefCounter(blank);
+    spec->parse = parse;
+    spec->format = format;
+
+    return spec;
+}
+
+
+bool pmConceptRegister(psMetadataItem *blank, // Blank value; contains the name
+                       pmConceptParseFunc parse, // Function to call to parse the concept
+                       pmConceptFormatFunc format, // Function to call to format the concept
+                       pmFPALevel level // Level at which to store concept in the FPA hierarchy
+                      )
+{
+    PS_ASSERT_PTR_NON_NULL(blank, false);
+
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+
+    pmConceptSpec *spec = pmConceptSpecAlloc(blank, parse, format); // The concept specification
+    psMetadata **target = NULL;         // The metadata of known concepts to write to
+    switch (level) {
+    case PM_FPA_LEVEL_FPA:
+        target = &conceptsFPA;
+        break;
+    case PM_FPA_LEVEL_CHIP:
+        target = &conceptsChip;
+        break;
+    case PM_FPA_LEVEL_CELL:
+        target = &conceptsCell;
+        break;
+    default:
+        psError(PS_ERR_IO, true, "Invalid concept level provided: %d\n", level);
+        psFree(spec);
+        return false;
+    }
+
+    psMetadataAdd(*target, PS_LIST_TAIL, blank->name, PS_DATA_UNKNOWN | PS_META_REPLACE,
+                  "Concepts specification", spec);
+    psFree(spec);                       // Drop reference
+
+    return true;
+}
+
+
+// Set all registered concepts to blank value for the specified level
+static bool conceptsBlank(psMetadata **specs, // One of the concepts specifications
+                          psMetadata *target // Place to install the concepts
+                         )
+{
+    assert(specs);
+    assert(target);
+
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(*specs, PS_LIST_HEAD, NULL); // Iterator on specs
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        psTrace("psModules.concepts", 9, "Blanking %s...\n", specItem->name);
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psMetadataItem *blank = spec->blank; // The concept
+        psMetadataItem *copy = NULL;    // Copy of the blank concept
+        // Trap the lists, which can't be copied in the ordinary way without a warning
+        if (blank->type == PS_DATA_LIST) {
+            copy = psMetadataItemAllocPtr(blank->name, PS_DATA_LIST, blank->comment, blank->data.V);
+        } else {
+            copy = psMetadataItemCopy(blank);
+        }
+        if (!psMetadataAddItem(target, copy, PS_LIST_TAIL, PS_META_REPLACE)) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to add blank version of concept %s\n", blank->name);
+        }
+        psFree(copy);                   // Drop reference
+    }
+    psFree(specsIter);
+
+    return true;
+}
+
+
+
+// Read all registered concepts for the specified level
+static bool conceptsRead(psMetadata **specs, // One of the concepts specifications
+                         pmFPA *fpa,    // The FPA
+                         pmChip *chip,  // The chip
+                         pmCell *cell,  // The cell
+                         unsigned int *read,     // What's already been read
+                         pmConceptSource source, // The source of the concepts to read
+                         psDB *db,      // Database handle
+                         psMetadata *target // Place into which to read the concepts
+                        )
+{
+    assert(specs);
+    assert(read);
+    assert(target);
+
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+
+    bool success = true;                // Success in reading concepts?
+    if (source & PM_CONCEPT_SOURCE_CAMERA && !(*read & PM_CONCEPT_SOURCE_CAMERA)) {
+        if (pmConceptsReadFromCamera(*specs, cell, target)) {
+            *read |= PM_CONCEPT_SOURCE_CAMERA;
+        } else {
+            psError(PS_ERR_UNKNOWN, false, "Error reading concepts from camera configuration.\n");
+            success = false;
+        }
+    }
+
+    if (source & PM_CONCEPT_SOURCE_DEFAULTS && !(*read & PM_CONCEPT_SOURCE_DEFAULTS)) {
+        if (pmConceptsReadFromDefaults(*specs, fpa, chip, cell, target)) {
+            *read |= PM_CONCEPT_SOURCE_DEFAULTS;
+        } else {
+            psError(PS_ERR_UNKNOWN, false, "Error reading concepts from defaults.\n");
+            success = false;
+        }
+    }
+
+    if (source & PM_CONCEPT_SOURCE_PHU && !(*read & PM_CONCEPT_SOURCE_PHU)) {
+        if (!pmConceptsReadFromHeader(*specs, fpa, chip, cell, target)) {
+            psError(PS_ERR_UNKNOWN, false, "Error reading concepts from header.\n");
+            success = false;
+        }
+    }
+
+    if (source & PM_CONCEPT_SOURCE_HEADER && !(*read & PM_CONCEPT_SOURCE_HEADER)) {
+        if (pmConceptsReadFromHeader(*specs, fpa, chip, cell, target)) {
+            *read |= PM_CONCEPT_SOURCE_HEADER;
+        } else {
+            psError(PS_ERR_UNKNOWN, false, "Error reading concepts from header.\n");
+            success = false;
+        }
+    }
+
+    #ifndef OMIT_PSDB
+    if (source & PM_CONCEPT_SOURCE_DATABASE && !(*read & PM_CONCEPT_SOURCE_DATABASE)) {
+        if (pmConceptsReadFromDatabase(*specs, fpa, chip, cell, db, target)) {
+            *read |= PM_CONCEPT_SOURCE_DATABASE;
+        } else {
+            psError(PS_ERR_UNKNOWN, false, "Error reading concepts from database.\n");
+            success = false;
+        }
+    }
+    #endif
+
+    return success;
+}
+
+// Write all registered concepts for the specified level
+static bool conceptsWrite(psMetadata **specs, // One of the concepts specifications
+                          pmFPA *fpa,   // The FPA
+                          pmChip *chip, // The chip
+                          pmCell *cell, // The cell
+                          pmConceptSource source, // The source of the concepts to write
+                          psDB *db,      // Database handle
+                          psMetadata *concepts // The concepts to write out
+                         )
+{
+    assert(specs);
+    assert(concepts);
+
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+
+    psTrace("psModules.concepts", 3, "Writing concepts (%p %p %p): %d\n", fpa, chip, cell, source);
+
+    if (source & PM_CONCEPT_SOURCE_CAMERA) {
+        pmConceptsWriteToCamera(*specs, cell, concepts);
+    }
+    if (source & PM_CONCEPT_SOURCE_DEFAULTS) {
+        pmConceptsWriteToDefaults(*specs, fpa, chip, cell, concepts);
+    }
+    if (source & (PM_CONCEPT_SOURCE_PHU | PM_CONCEPT_SOURCE_HEADER)) {
+        pmConceptsWriteToHeader(*specs, fpa, chip, cell, concepts);
+    }
+    if (source & PM_CONCEPT_SOURCE_DATABASE) {
+        pmConceptsWriteToDatabase(*specs, fpa, chip, cell, db, concepts);
+    }
+
+    return true;
+}
+
+// Set the concepts for a given FPA to blanks
+bool pmConceptsBlankFPA(pmFPA *fpa    // FPA for which to set blank concepts
+                       )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    psTrace("psModules.concepts", 5, "Blanking FPA concepts: %p %p\n", conceptsFPA, fpa->concepts);
+    return conceptsBlank(&conceptsFPA, fpa->concepts);
+}
+
+// Read the concepts for the given set of fpa, chip, cell
+bool pmConceptsRead(pmFPA *fpa,         // FPA for which to read concepts
+                    pmChip *chip,       // Chip for which to read concepts, or NULL
+                    pmCell *cell,       // Cell for which to read concepts, or NULL
+                    pmConceptSource source, // The source of the concepts to read
+                    psDB *db            // Database handle
+                   )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    bool success = conceptsRead(&conceptsFPA, fpa, chip, cell, &fpa->conceptsRead, source, db, fpa->concepts);
+    if (chip) {
+        success |= conceptsRead(&conceptsChip, fpa, chip, cell, &chip->conceptsRead, source, db, chip->concepts);
+    }
+    if (cell) {
+        success |= conceptsRead(&conceptsCell, fpa, chip, cell, &cell->conceptsRead, source, db, cell->concepts);
+    }
+
+    return success;
+}
+
+
+// Read the concepts for a given FPA
+bool pmConceptsReadFPA(pmFPA *fpa,      // FPA for which to read concepts
+                       pmConceptSource source, // The source of the concepts to read
+                       bool propagateDown, // Propagate to lower levels?
+                       psDB *db        // Database handle
+                      )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    psTrace("psModules.concepts", 5, "Reading FPA concepts: %p %p\n", conceptsFPA, fpa->concepts);
+    bool success = conceptsRead(&conceptsFPA, fpa, NULL, NULL, &fpa->conceptsRead, source, db, fpa->concepts);
+    if (propagateDown) {
+        psArray *chips = fpa->chips;    // Array of chips
+        for (long i = 0; i < chips->n; i++) {
+            pmChip *chip = chips->data[i]; // Chip of interest
+            if (chip && !chip->hdu) {
+                success |= pmConceptsReadChip(chip, source, false, true, db);
+            }
+        }
+    }
+
+    return success;
+}
+
+// Read the concepts for a given FPA
+bool pmConceptsWriteFPA(pmFPA *fpa,     // FPA for which to write concepts
+                        pmConceptSource source, // The source of the concepts to read
+                        bool propagateDown, // Propagate to lower levels?
+                        psDB *db        // Database handle
+                       )
+{
+    PS_ASSERT_PTR_NON_NULL(fpa, false);
+    psTrace("psModules.concepts", 5, "Writing FPA concepts: %p %p\n", conceptsFPA, fpa->concepts);
+    bool success = conceptsWrite(&conceptsFPA, fpa, NULL, NULL, source, db, fpa->concepts);
+    if (propagateDown) {
+        psArray *chips = fpa->chips;        // Array of chips
+        for (long i = 0; i < chips->n; i++) {
+            pmChip *chip = chips->data[i];  // Chip of interest
+            if (chip && !chip->hdu) {
+                success |= pmConceptsWriteChip(chip, source, false, true, db);
+            }
+        }
+    }
+    return success;
+}
+
+// Set the concepts for a given chip to blanks
+bool pmConceptsBlankChip(pmChip *chip // FPA for which to set blank concepts
+                        )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    psTrace("psModules.concepts", 5, "Blanking chip concepts: %p %p\n", conceptsChip, chip->concepts);
+    return conceptsBlank(&conceptsChip, chip->concepts);
+}
+
+// Read the concepts for a given FPA
+bool pmConceptsReadChip(pmChip *chip,   // Chip for which to read concepts
+                        pmConceptSource source, // The source of the concepts to read
+                        bool propagateUp, // Propagate to higher levels?
+                        bool propagateDown, // Propagate to lower levels?
+                        psDB *db        // Database handle
+                       )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    psTrace("psModules.concepts", 5, "Reading chip concepts: %p %p\n", conceptsChip, chip->concepts);
+    pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
+    bool success = conceptsRead(&conceptsChip, fpa, chip, NULL, &chip->conceptsRead, source, db,
+                                chip->concepts);
+    if (propagateUp && !fpa->hdu) {
+        success |= conceptsRead(&conceptsFPA, fpa, chip, NULL, &fpa->conceptsRead, source, db, fpa->concepts);
+    }
+    if (propagateDown) {
+        psArray *cells = chip->cells;        // Array of cells
+        for (long i = 0; i < cells->n; i++) {
+            pmCell *cell = cells->data[i];  // Cell of interest
+            if (cell && !cell->hdu) {
+                success |= pmConceptsReadCell(cell, source, false, db);
+            }
+        }
+    }
+    return success;
+}
+
+// Read the concepts for a given FPA
+bool pmConceptsWriteChip(pmChip *chip,  // Chip for which to write concepts
+                         pmConceptSource source, // The source of the concepts to read
+                         bool propagateUp, // Propagate to higher levels?
+                         bool propagateDown, // Propagate to lower levels?
+                         psDB *db        // Database handle
+                        )
+{
+    PS_ASSERT_PTR_NON_NULL(chip, false);
+    psTrace("psModules.concepts", 5, "Writing chip concepts: %p %p\n", conceptsChip, chip->concepts);
+    pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
+    bool success = conceptsWrite(&conceptsChip, fpa, chip, NULL, source, db, chip->concepts);
+    if (propagateUp && !fpa->hdu) {
+        success |= conceptsWrite(&conceptsFPA, fpa, chip, NULL, source, db, fpa->concepts);
+    }
+    if (propagateDown) {
+        psArray *cells = chip->cells;        // Array of cells
+        for (long i = 0; i < cells->n; i++) {
+            pmCell *cell = cells->data[i];  // Cell of interest
+            if (cell && !cell->hdu) {
+                success |= pmConceptsWriteCell(cell, source, false, db);
+            }
+        }
+    }
+    return success;
+}
+
+// Set the concepts for a given chip to blanks
+bool pmConceptsBlankCell(pmCell *cell // Cell for which to set blank concepts
+                        )
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    psTrace("psModules.concepts", 5, "Blanking cell concepts: %p %p\n", conceptsCell, cell->concepts);
+    return conceptsBlank(&conceptsCell, cell->concepts);
+}
+
+// Read the concepts for a given FPA
+bool pmConceptsReadCell(pmCell *cell,   // Cell for which to read concepts
+                        pmConceptSource source, // The source of the concepts to read
+                        bool propagateUp, // Propagate to higher levels?
+                        psDB *db        // Database handle
+                       )
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    psTrace("psModules.concepts", 5, "Reading cell concepts: %p %p\n", conceptsCell, cell->concepts);
+    pmChip *chip = cell->parent;        // Chip to which the cell belongs
+    pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
+
+    bool success = conceptsRead(&conceptsCell, fpa, chip, cell, &cell->conceptsRead, source, db,
+                                cell->concepts);
+    if (propagateUp) {
+        if (!chip->hdu) {
+            success |= conceptsRead(&conceptsChip, fpa, chip, cell, &chip->conceptsRead, source, db,
+                                    chip->concepts);
+            if (!fpa->hdu) {
+                success |= conceptsRead(&conceptsFPA, fpa, chip, cell, &fpa->conceptsRead, source, db,
+                                        fpa->concepts);
+            }
+        }
+    }
+
+    return success;
+}
+
+// Read the concepts for a given FPA
+bool pmConceptsWriteCell(pmCell *cell,  // FPA for which to write concepts
+                         pmConceptSource source, // The source of the concepts to read
+                         bool propagateUp, // Propagate to higher levels?
+                         psDB *db       // Database handle
+                        )
+{
+    PS_ASSERT_PTR_NON_NULL(cell, false);
+    psTrace("psModules.concepts", 5, "Writing cell concepts: %p %p\n", conceptsCell, cell->concepts);
+    pmChip *chip = cell->parent;        // Chip to which the cell belongs
+    pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
+
+    bool success = conceptsWrite(&conceptsCell, fpa, chip, cell, source, db, cell->concepts);
+    if (propagateUp) {
+        if (!chip->hdu) {
+            success |= conceptsWrite(&conceptsChip, fpa, chip, cell, source, db, chip->concepts);
+            if (!fpa->hdu) {
+                success |= conceptsWrite(&conceptsFPA, fpa, chip, cell, source, db, fpa->concepts);
+            }
+        }
+    }
+
+    return success;
+}
+
+
+bool pmConceptsInit(void)
+{
+    conceptsInitialised = true;
+
+    bool init = false;                  // Did we initialise anything?
+    if (! conceptsFPA) {
+        conceptsFPA = psMetadataAlloc();
+        init = true;
+
+        // Install the standard concepts
+
+        #if 0
+        // FPA.NAME
+        {
+            psMetadataItem *fpaName = psMetadataItemAllocStr("FPA.NAME", "Name of FPA", "");
+            pmConceptRegister(fpaName, NULL, NULL, PM_FPA_LEVEL_FPA);
+            psFree(fpaName);
+        }
+        #endif
+
+        // FPA.CAMERA
+        {
+            psMetadataItem *fpaCamera = psMetadataItemAllocStr("FPA.CAMERA", "Camera used", "");
+            pmConceptRegister(fpaCamera, NULL, NULL, PM_FPA_LEVEL_FPA);
+            psFree(fpaCamera);
+        }
+
+        // FPA.FOCUS
+        {
+            psMetadataItem *fpaFocus = psMetadataItemAllocF32("FPA.FOCUS", "Telescope focus", NAN);
+            pmConceptRegister(fpaFocus, NULL, NULL, PM_FPA_LEVEL_FPA);
+            psFree(fpaFocus);
+        }
+
+        // FPA.AIRMASS
+        {
+            psMetadataItem *fpaAirmass = psMetadataItemAllocF32("FPA.AIRMASS", "Airmass at boresight", NAN);
+            pmConceptRegister(fpaAirmass, NULL, NULL, PM_FPA_LEVEL_FPA);
+            psFree(fpaAirmass);
+        }
+
+        // FPA.FILTER
+        {
+            psMetadataItem *fpaFilter = psMetadataItemAllocStr("FPA.FILTER", "Filter used", "");
+            pmConceptRegister(fpaFilter, NULL, NULL, PM_FPA_LEVEL_FPA);
+            psFree(fpaFilter);
+        }
+
+        // FPA.POSANGLE
+        {
+            psMetadataItem *fpaPosangle = psMetadataItemAllocF32("FPA.POSANGLE",
+                                          "Position angle of instrument", NAN);
+            pmConceptRegister(fpaPosangle, NULL, NULL, PM_FPA_LEVEL_FPA);
+            psFree(fpaPosangle);
+        }
+
+        // FPA.RADECSYS
+        {
+            psMetadataItem *fpaRadecsys = psMetadataItemAllocStr("FPA.RADECSYS",
+                                          "Celestial coordinate system", "");
+            pmConceptRegister(fpaRadecsys, NULL, NULL, PM_FPA_LEVEL_FPA);
+            psFree(fpaRadecsys);
+        }
+
+        // FPA.RA
+        {
+            psMetadataItem *fpaRa = psMetadataItemAllocF64("FPA.RA", "Right Ascension of boresight", NAN);
+            pmConceptRegister(fpaRa, (pmConceptParseFunc)p_pmConceptParse_FPA_Coords,
+                              (pmConceptFormatFunc)p_pmConceptFormat_FPA_Coords, PM_FPA_LEVEL_FPA);
+            psFree(fpaRa);
+        }
+
+        // FPA.DEC
+        {
+            psMetadataItem *fpaDec = psMetadataItemAllocF64("FPA.DEC", "Declination of boresight", NAN);
+            pmConceptRegister(fpaDec, (pmConceptParseFunc)p_pmConceptParse_FPA_Coords,
+                              (pmConceptFormatFunc)p_pmConceptFormat_FPA_Coords, PM_FPA_LEVEL_FPA);
+            psFree(fpaDec);
+        }
+
+        // FPA.OBSTYPE
+        {
+            psMetadataItem *fpaObstype = psMetadataItemAllocStr("FPA.OBSTYPE", "Type of observation", "");
+            pmConceptRegister(fpaObstype, NULL, NULL, PM_FPA_LEVEL_FPA);
+            psFree(fpaObstype);
+        }
+
+        // FPA.OBJECT
+        {
+            psMetadataItem *fpaObject = psMetadataItemAllocStr("FPA.OBJECT", "Object of observation", "");
+            pmConceptRegister(fpaObject, NULL, NULL, PM_FPA_LEVEL_FPA);
+            psFree(fpaObject);
+        }
+
+        // FPA.ALT
+        {
+            psMetadataItem *fpaAlt = psMetadataItemAllocF64("FPA.ALT", "Altitude of telescope", NAN);
+            pmConceptRegister(fpaAlt, NULL, NULL, PM_FPA_LEVEL_FPA);
+            psFree(fpaAlt);
+        }
+
+        // FPA.AZ
+        {
+            psMetadataItem *fpaAz = psMetadataItemAllocF64("FPA.AZ", "Azimuth of telescope", NAN);
+            pmConceptRegister(fpaAz, NULL, NULL, PM_FPA_LEVEL_FPA);
+            psFree(fpaAz);
+        }
+
+        // FPA.TIMESYS
+        {
+            psMetadataItem *fpaTimesys = psMetadataItemAllocS32("FPA.TIMESYS", "Time system", -1);
+            pmConceptRegister(fpaTimesys, (pmConceptParseFunc)p_pmConceptParse_TIMESYS,
+                              (pmConceptFormatFunc)p_pmConceptFormat_TIMESYS, PM_FPA_LEVEL_FPA);
+            psFree(fpaTimesys);
+        }
+
+        // FPA.TIME
+        {
+            psTime *time = psTimeAlloc(PS_TIME_TAI); // Blank time
+            // Not particularly distinguishing, but should be good enough
+            time->sec = 0;
+            time->nsec = 0;
+            psMetadataItem *fpaTime = psMetadataItemAlloc("FPA.TIME", PS_DATA_TIME,
+                                      "Time of exposure", time);
+            psFree(time);
+            pmConceptRegister(fpaTime, (pmConceptParseFunc)p_pmConceptParse_TIME,
+                              (pmConceptFormatFunc)p_pmConceptFormat_TIME, PM_FPA_LEVEL_FPA);
+            psFree(fpaTime);
+        }
+
+        // Done with FPA level concepts
+    }
+    if (! conceptsChip) {
+        conceptsChip = psMetadataAlloc();
+        init = true;
+
+        // Install the standard concepts
+
+        // CHIP.XPARITY
+        {
+            psMetadataItem *chipXparity = psMetadataItemAllocS32("CHIP.XPARITY",
+                                          "Orientation in x compared to the rest of the FPA", 0);
+            pmConceptRegister(chipXparity, NULL, NULL, PM_FPA_LEVEL_CHIP);
+            psFree(chipXparity);
+        }
+
+        // CHIP.YPARITY
+        {
+            psMetadataItem *chipYparity = psMetadataItemAllocS32("CHIP.YPARITY",
+                                          "Orientation in x compared to the rest of the FPA", 0);
+            pmConceptRegister(chipYparity, NULL, NULL, PM_FPA_LEVEL_CHIP);
+            psFree(chipYparity);
+        }
+
+        // CHIP.X0
+        {
+            psMetadataItem *chipX0 = psMetadataItemAllocS32("CHIP.X0", "Position of (0,0) on the FPA", 0);
+            pmConceptRegister(chipX0, (pmConceptParseFunc)p_pmConceptParse_Positions,
+                              (pmConceptFormatFunc)p_pmConceptFormat_Positions, PM_FPA_LEVEL_CHIP);
+            psFree(chipX0);
+        }
+
+        // CHIP.Y0
+        {
+            psMetadataItem *chipY0 = psMetadataItemAllocS32("CHIP.Y0", "Position of (0,0) on the FPA", 0);
+            pmConceptRegister(chipY0, (pmConceptParseFunc)p_pmConceptParse_Positions,
+                              (pmConceptFormatFunc)p_pmConceptFormat_Positions, PM_FPA_LEVEL_CHIP);
+            psFree(chipY0);
+        }
+
+        // CHIP.TEMP
+        {
+            psMetadataItem *chipTemp = psMetadataItemAllocF32("CHIP.TEMP", "Temperature of chip", NAN);
+            pmConceptRegister(chipTemp, NULL, NULL, PM_FPA_LEVEL_CHIP);
+            psFree(chipTemp);
+        }
+
+        // Done with chip level concepts
+    }
+    if (! conceptsCell) {
+        conceptsCell = psMetadataAlloc();
+        init = true;
+
+        // Install the standard concepts
+
+        // CELL.GAIN
+        {
+            psMetadataItem *cellGain = psMetadataItemAllocF32("CELL.GAIN", "CCD gain (e/count)", NAN);
+            pmConceptRegister(cellGain, NULL, NULL, PM_FPA_LEVEL_CELL);
+            psFree(cellGain);
+        }
+
+        // CELL.READNOISE
+        {
+            psMetadataItem *cellReadnoise = psMetadataItemAllocF32("CELL.READNOISE",
+                                            "CCD read noise (e)", NAN);
+            pmConceptRegister(cellReadnoise, NULL, NULL, PM_FPA_LEVEL_CELL);
+            psFree(cellReadnoise);
+        }
+
+        // CELL.SATURATION
+        {
+            psMetadataItem *cellSaturation = psMetadataItemAllocF32("CELL.SATURATION",
+                                             "Saturation level (counts)", NAN);
+            pmConceptRegister(cellSaturation, NULL, NULL, PM_FPA_LEVEL_CELL);
+            psFree(cellSaturation);
+        }
+
+        // CELL.BAD
+        {
+            psMetadataItem *cellBad = psMetadataItemAllocF32("CELL.BAD", "Bad level (counts)", NAN);
+            pmConceptRegister(cellBad, NULL, NULL, PM_FPA_LEVEL_CELL);
+            psFree(cellBad);
+        }
+
+        // CELL.XPARITY
+        {
+            psMetadataItem *cellXparity = psMetadataItemAllocS32("CELL.XPARITY",
+                                          "Orientation in x compared to the rest of the chip", 0);
+            pmConceptRegister(cellXparity, NULL, NULL, PM_FPA_LEVEL_CELL);
+            psFree(cellXparity);
+        }
+
+        // CELL.YPARITY
+        {
+            psMetadataItem *cellYparity = psMetadataItemAllocS32("CELL.YPARITY",
+                                          "Orientation in x compared to the rest of the chip", 0);
+            pmConceptRegister(cellYparity, NULL, NULL, PM_FPA_LEVEL_CELL);
+            psFree(cellYparity);
+        }
+
+        // CELL.READDIR
+        {
+            psMetadataItem *cellReaddir = psMetadataItemAllocS32("CELL.READDIR",
+                                          "Read direction, rows=1, cols=2", 0);
+            pmConceptRegister(cellReaddir, NULL, NULL, PM_FPA_LEVEL_CELL);
+            psFree(cellReaddir);
+        }
+
+
+        // These (CELL.EXPOSURE and CELL.DARKTIME) used to be READOUT.EXPOSURE and READOUT.DARKTIME, but that
+        // doesn't really make sense at the moment.  Maybe we need to add a "parent" link to the readouts.
+        // But then how are the exposure times REALLY derived?  They're not in the FITS headers, because a
+        // readout is a plane in a 3D image.  We'll have to dream up some additional suffix to specify these,
+        // but for now....
+
+        // CELL.EXPOSURE
+        {
+            psMetadataItem *cellExposure = psMetadataItemAllocF32("CELL.EXPOSURE",
+                                           "Exposure time (sec)", NAN);
+            pmConceptRegister(cellExposure, NULL, NULL, PM_FPA_LEVEL_CELL);
+            psFree(cellExposure);
+        }
+
+        // CELL.DARKTIME
+        {
+            psMetadataItem *cellDarktime = psMetadataItemAllocF32("CELL.DARKTIME",
+                                           "Time since flush (sec)", NAN);
+            pmConceptRegister(cellDarktime, NULL, NULL, PM_FPA_LEVEL_CELL);
+            psFree(cellDarktime);
+        }
+
+        // CELL.TRIMSEC
+        {
+            psRegion *trimsec = psAlloc(sizeof(psRegion)); // Blank trimsec
+            trimsec->x0 = trimsec->y0 = trimsec->x1 = trimsec->y1 = NAN;
+            psMetadataItem *cellTrimsec = psMetadataItemAllocPtr("CELL.TRIMSEC", PS_DATA_REGION,
+                                          "Trim section", trimsec);
+            psFree(trimsec);
+            pmConceptRegister(cellTrimsec, (pmConceptParseFunc)p_pmConceptParse_CELL_TRIMSEC,
+                              (pmConceptFormatFunc)p_pmConceptFormat_CELL_TRIMSEC, PM_FPA_LEVEL_CELL);
+            psFree(cellTrimsec);
+        }
+
+        // CELL.BIASSEC
+        {
+            psList *biassecs = psListAlloc(NULL); // Blank biassecs
+            psMetadataItem *cellBiassec = psMetadataItemAllocPtr("CELL.BIASSEC", PS_DATA_LIST,
+                                          "Bias sections", biassecs);
+            psFree(biassecs);
+            pmConceptRegister(cellBiassec, (pmConceptParseFunc)p_pmConceptParse_CELL_BIASSEC,
+                              (pmConceptFormatFunc)p_pmConceptFormat_CELL_BIASSEC, PM_FPA_LEVEL_CELL);
+            psFree(cellBiassec);
+        }
+
+        // CELL.XBIN
+        {
+            psMetadataItem *cellXbin = psMetadataItemAllocS32("CELL.XBIN", "Binning in x", 0);
+            pmConceptRegister(cellXbin, (pmConceptParseFunc)p_pmConceptParse_CELL_Binning,
+                              (pmConceptFormatFunc)p_pmConceptFormat_CELL_XBIN, PM_FPA_LEVEL_CELL);
+            psFree(cellXbin);
+        }
+
+        // CELL.YBIN
+        {
+            psMetadataItem *cellYbin = psMetadataItemAllocS32("CELL.YBIN", "Binning in y", 0);
+            pmConceptRegister(cellYbin, (pmConceptParseFunc)p_pmConceptParse_CELL_Binning,
+                              (pmConceptFormatFunc)p_pmConceptFormat_CELL_YBIN, PM_FPA_LEVEL_CELL);
+            psFree(cellYbin);
+        }
+
+        // CELL.TIMESYS
+        {
+            psMetadataItem *cellTimesys = psMetadataItemAllocS32("CELL.TIMESYS", "Time system", -1);
+            pmConceptRegister(cellTimesys, (pmConceptParseFunc)p_pmConceptParse_TIMESYS,
+                              (pmConceptFormatFunc)p_pmConceptFormat_TIMESYS, PM_FPA_LEVEL_CELL);
+            psFree(cellTimesys);
+        }
+
+        // CELL.TIME
+        {
+            psTime *time = psTimeAlloc(PS_TIME_TAI); // Blank time
+            // Not particularly distinguishing, but should be good enough
+            time->sec = 0;
+            time->nsec = 0;
+            psMetadataItem *cellTime = psMetadataItemAlloc("CELL.TIME", PS_DATA_TIME,
+                                       "Time of exposure", time);
+            psFree(time);
+            pmConceptRegister(cellTime, (pmConceptParseFunc)p_pmConceptParse_TIME,
+                              (pmConceptFormatFunc)p_pmConceptFormat_TIME, PM_FPA_LEVEL_CELL);
+            psFree(cellTime);
+        }
+
+        // CELL.X0
+        {
+            psMetadataItem *cellX0 = psMetadataItemAllocS32("CELL.X0", "Position of (0,0) on the chip", 0);
+            pmConceptRegister(cellX0, (pmConceptParseFunc)p_pmConceptParse_Positions,
+                              (pmConceptFormatFunc)p_pmConceptFormat_Positions, PM_FPA_LEVEL_CELL);
+            psFree(cellX0);
+        }
+
+        // CELL.Y0
+        {
+            psMetadataItem *cellY0 = psMetadataItemAllocS32("CELL.Y0", "Position of (0,0) on the chip", 0);
+            pmConceptRegister(cellY0, (pmConceptParseFunc)p_pmConceptParse_Positions,
+                              (pmConceptFormatFunc)p_pmConceptFormat_Positions, PM_FPA_LEVEL_CELL);
+            psFree(cellY0);
+        }
+
+    }
+
+    return init;
+}
+
+void pmConceptsDone(void)
+{
+    psFree(conceptsFPA);
+    conceptsFPA = NULL;
+    psFree(conceptsChip);
+    conceptsChip = NULL;
+    psFree(conceptsCell);
+    conceptsCell = NULL;
+
+    conceptsInitialised = false;
+}
+
+
+// Copy concepts from one FPA to another
+bool pmFPACopyConcepts(pmFPA *target,   // The target FPA
+                       pmFPA *source    // The target FPA
+                      )
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+
+    // Copy FPA concepts
+    target->concepts = psMetadataCopy(target->concepts, source->concepts);
+
+    // Copy chip concepts
+    psArray *targetChips = target->chips; // Chips in target
+    psArray *sourceChips = source->chips; // Chips in source
+    if (targetChips->n != sourceChips->n) {
+        psError(PS_ERR_IO, true, "Number of chips in target (%ld) and source (%ld) differ --- unable to copy "
+                "concepts.\n", targetChips->n, sourceChips->n);
+        return false;
+    }
+    for (int i = 0; i < targetChips->n; i++) {
+        pmChip *targetChip = targetChips->data[i]; // Target chip of interest
+        pmChip *sourceChip = sourceChips->data[i]; // Source chip of interest
+        if (! targetChip || ! sourceChip) {
+            continue;
+        }
+        targetChip->concepts = psMetadataCopy(targetChip->concepts, sourceChip->concepts);
+
+        // Copy cell concepts
+        psArray *targetCells = targetChip->cells; // Cells in target
+        psArray *sourceCells = sourceChip->cells; // Cells in source
+        if (targetCells->n != sourceCells->n) {
+            psError(PS_ERR_IO, true, "Number of cells in target (%ld) and source (%ld) differ for chip %d ---"
+                    " unable to copy concepts.\n", targetCells->n, sourceCells->n, i);
+            return false;
+        }
+        for (int j = 0; j < targetCells->n; j++) {
+            pmCell *targetCell = targetCells->data[j]; // Target chip of interest
+            pmCell *sourceCell = sourceCells->data[j]; // Source chip of interest
+            if (! targetCell || ! sourceCell) {
+                continue;
+            }
+            targetCell->concepts = psMetadataCopy(targetCell->concepts, sourceCell->concepts);
+        }
+    }
+
+    return true;
+}
Index: /tags/RHL_v1_1/psModules/src/concepts/pmConcepts.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/concepts/pmConcepts.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/concepts/pmConcepts.h	(revision 9533)
@@ -0,0 +1,104 @@
+#ifndef PM_CONCEPTS_H
+#define PM_CONCEPTS_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+// Function to call to parse a concept once it has been read
+typedef psMetadataItem* (*pmConceptParseFunc)(const psMetadataItem *concept, const psMetadataItem *pattern, const psMetadata *cameraFormat, const pmFPA *fpa, const pmChip *chip, const pmCell *cell);
+// Function to call to format a concept for writing
+typedef psMetadataItem* (*pmConceptFormatFunc)(const psMetadataItem *concept, const psMetadata *cameraFormat, const pmFPA *fpa, const pmChip *chip, const pmCell *cell);
+
+// A "concept" specification
+typedef struct
+{
+    psMetadataItem *blank;              // Blank value of concept; also contains the name
+    pmConceptParseFunc parse;           // Function to call to read the concept
+    pmConceptFormatFunc format;         // Function to call to write the concept
+}
+pmConceptSpec;
+
+// Allocator
+pmConceptSpec *pmConceptSpecAlloc(psMetadataItem *blank, // Blank value; contains the name
+                                  pmConceptParseFunc parse, // Function to call to parse the concept
+                                  pmConceptFormatFunc format // Function to call to format the concept
+                                 );
+
+// Register a new concept
+bool pmConceptRegister(psMetadataItem *blank, // Blank value; contains the name
+                       pmConceptParseFunc parse, // Function to call to parse the concept
+                       pmConceptFormatFunc format, // Function to call to format the concept
+                       pmFPALevel level // Level at which to store concept in the FPA hierarchy
+                      );
+
+// Some specificity to reading and writing concepts
+typedef enum {
+    PM_CONCEPT_SOURCE_NONE     = 0x00,  // No concepts
+    PM_CONCEPT_SOURCE_CAMERA   = 0x01,  // Concept comes from the camera information
+    PM_CONCEPT_SOURCE_DEFAULTS = 0x02,  // Concept comes from defaults
+    PM_CONCEPT_SOURCE_PHU      = 0x04,  // Concept comes from PHU
+    PM_CONCEPT_SOURCE_HEADER   = 0x08,  // Concept comes from FITS header
+    PM_CONCEPT_SOURCE_DATABASE = 0x10,  // Concept comes from database
+    PM_CONCEPT_SOURCE_ALL      = 0xff   // All concepts
+} pmConceptSource;
+
+// Read the concepts for the given set of fpa, chip, cell
+bool pmConceptsRead(pmFPA *fpa,         // FPA for which to read concepts
+                    pmChip *chip,       // Chip for which to read concepts, or NULL
+                    pmCell *cell,       // Cell for which to read concepts, or NULL
+                    pmConceptSource source, // The source of the concepts to read
+                    psDB *db            // Database handle
+                   );
+
+// Set blanks, read or write concepts at the appropriate level
+bool pmConceptsBlankFPA(pmFPA *fpa      // FPA for which to set blank concepts
+                       );
+bool pmConceptsReadFPA(pmFPA *fpa,      // FPA for which to read concepts
+                       pmConceptSource source, // Source for concepts
+                       bool propagateDown, // Propagate to lower levels?
+                       psDB *db         // Database handle
+                      );
+bool pmConceptsWriteFPA(pmFPA *fpa,     // FPA for which to write concepts
+                        pmConceptSource source, // Source for concepts
+                        bool propagateDown, // Propagate to lower levels?
+                        psDB *db        // Database handle
+                       );
+bool pmConceptsBlankChip(pmChip *chip   // FPA for which to set blank concepts
+                        );
+bool pmConceptsReadChip(pmChip *chip,   // Chip for which to read concepts
+                        pmConceptSource source, // Source for concepts
+                        bool propagateUp, // Propagate to higher levels?
+                        bool propagateDown, // Propagate to lower levels?
+                        psDB *db        // Database handle
+                       );
+bool pmConceptsWriteChip(pmChip *chip,  // Chip for which to write concepts
+                         pmConceptSource source, // Source for concepts
+                         bool propagateUp,// Propagate to higher levels?
+                         bool propagateDown, // Propagate to lower levels?
+                         psDB *db       // Database handle
+                        );
+bool pmConceptsBlankCell(pmCell *cell   // Cell for which to set blank concepts
+                        );
+bool pmConceptsReadCell(pmCell *cell,   // Cell for which to read concepts
+                        pmConceptSource source, // Source for concepts
+                        bool propagateUp, // Propagate to higher levels?
+                        psDB *db        // Database handle
+                       );
+bool pmConceptsWriteCell(pmCell *cell,  // FPA for which to write concepts
+                         pmConceptSource source, // Source for concepts
+                         bool propagateUp, // Propagate to higher levels?
+                         psDB *db       // Database handle
+                        );
+
+// Set up the blank concepts
+bool pmConceptsInit(void);
+// Free the concept specs so there's no memory leak
+void pmConceptsDone(void);
+
+// Copy all the concepts within an FPA to another FPA
+bool pmFPACopyConcepts(pmFPA *target,   // The target FPA
+                       pmFPA *source    // The target FPA
+                      );
+
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/concepts/pmConceptsAverage.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/concepts/pmConceptsAverage.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/concepts/pmConceptsAverage.c	(revision 9533)
@@ -0,0 +1,124 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmConcepts.h"
+#include "pmFPA.h"
+
+#include "pmConceptsAverage.h"
+
+
+// Update a metadata entry directly
+#define MD_UPDATE(MD, NAME, TYPE, VALUE) \
+{ \
+    psMetadataItem *item = psMetadataLookup(MD, NAME); \
+    item->data.TYPE = VALUE; \
+}
+
+
+// Set a variety of concepts in a cell by averaging over several
+bool pmConceptsAverageCells(pmCell *target,// Target cell
+                            psList *sources, // List of source cells
+                            psRegion *trimsec, // The trim section
+                            psRegion *biassec // The bias section
+                           )
+{
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+    PS_ASSERT_INT_POSITIVE(sources->n, false);
+
+    bool success = true;                // Result of setting everything
+    float gain       = 0.0;             // Gain
+    float readnoise  = 0.0;             // Read noise
+    float saturation = INFINITY;        // Saturation level
+    float bad        = -INFINITY;       // Bad level
+    float exposure   = 0.0;             // Exposure time
+    float darktime   = 0.0;             // Dark time
+    double time      = 0.0;             // Time of observation
+    psTimeType timeSys = 0;             // Time system
+    int readdir      = 0;               // Cell read direction
+
+    int nCells = 0;                     // Number of cells;
+    psListIterator *sourcesIter = psListIteratorAlloc(sources, PS_LIST_HEAD, false); // Iterator for sources
+    pmCell *cell = NULL;                // Source cell from iteration
+    while ((cell = psListGetAndIncrement(sourcesIter))) {
+        if (!cell) {
+            continue;
+        }
+
+        nCells++;
+        gain       += psMetadataLookupF32(NULL, cell->concepts, "CELL.GAIN");
+        readnoise  += psMetadataLookupF32(NULL, cell->concepts, "CELL.READNOISE");
+        exposure   += psMetadataLookupF32(NULL, cell->concepts, "CELL.EXPOSURE");
+        darktime   += psMetadataLookupF32(NULL, cell->concepts, "CELL.DARKTIME");
+        psTime *cellTime = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TIME");
+        time       += psTimeToMJD(cellTime);
+        if (nCells == 1) {
+            timeSys = psMetadataLookupS32(NULL, cell->concepts, "CELL.TIMESYS");
+            readdir = psMetadataLookupS32(NULL, cell->concepts, "CELL.READDIR");
+        } else {
+            if (timeSys != psMetadataLookupS32(NULL, cell->concepts, "CELL.TIMESYS")) {
+                psLogMsg(__func__, PS_LOG_ERROR, "Differing time systems in use: %d vs %d\n", timeSys,
+                         psMetadataLookupS32(NULL, cell->concepts, "CELL.TIMESYS"));
+                success = false;
+            }
+            if (readdir != psMetadataLookupS32(NULL, cell->concepts, "CELL.READDIR")) {
+                psLogMsg(__func__, PS_LOG_ERROR, "Differing cell read directions in use: %d vs %d\n", readdir,
+                         psMetadataLookupS32(NULL, cell->concepts, "CELL.READDIR"));
+                success = false;
+            }
+        }
+
+        float cellSaturation = psMetadataLookupF32(NULL, cell->concepts, "CELL.SATURATION");
+        if (cellSaturation < saturation) {
+            saturation = cellSaturation;
+        }
+        float cellBad = psMetadataLookupF32(NULL, cell->concepts, "CELL.BAD");
+        if (cellBad > bad) {
+            bad = cellBad;
+        }
+    }
+    psFree(sourcesIter);
+
+    gain      /= (float)nCells;
+    readnoise /= (float)nCells;
+    exposure  /= (float)nCells;
+    darktime  /= (float)nCells;
+    time      /= (double)nCells;
+
+    MD_UPDATE(target->concepts, "CELL.GAIN", F32, gain);
+    MD_UPDATE(target->concepts, "CELL.READNOISE", F32, readnoise);
+    MD_UPDATE(target->concepts, "CELL.SATURATION", F32, saturation);
+    MD_UPDATE(target->concepts, "CELL.BAD", F32, bad);
+    MD_UPDATE(target->concepts, "CELL.EXPOSURE", F32, exposure);
+    MD_UPDATE(target->concepts, "CELL.DARKTIME", F32, darktime);
+    MD_UPDATE(target->concepts, "CELL.TIMESYS", S32, timeSys);
+    MD_UPDATE(target->concepts, "CELL.READDIR", S32, readdir);
+
+    // CELL.TIME needs special care
+    {
+        psMetadataItem *timeItem = psMetadataLookup(target->concepts, "CELL.TIME");
+        psFree(timeItem->data.V);
+        timeItem->data.V = psTimeFromMJD(time);
+    }
+
+    // CELL.TRIMSEC needs special care
+    if (trimsec) {
+        psMetadataItem *trimsecItem = psMetadataLookup(target->concepts, "CELL.TRIMSEC");
+        psFree(trimsecItem->data.V);
+        trimsecItem->data.V = psMemIncrRefCounter(trimsec);
+    }
+
+    // CELL.BIASSEC needs special care
+    if (biassec) {
+        psMetadataItem *biassecItem = psMetadataLookup(target->concepts, "CELL.BIASSEC");
+        psFree(biassecItem->data.V);
+        biassecItem->data.V = psMemIncrRefCounter(biassec);
+    }
+
+    return success;
+}
+
Index: /tags/RHL_v1_1/psModules/src/concepts/pmConceptsAverage.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/concepts/pmConceptsAverage.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/concepts/pmConceptsAverage.h	(revision 9533)
@@ -0,0 +1,14 @@
+#ifndef PM_CONCEPTS_AVERAGE_H
+#define PM_CONCEPTS_AVERAGE_H
+
+#include <pslib.h>
+#include "pmFPA.h"
+
+// Set a variety of concepts in a cell by averaging over several
+bool pmConceptsAverageCells(pmCell *target,// Target cell
+                            psList *sources, // List of source cells
+                            psRegion *trimsec, // The trim section
+                            psRegion *biassec // The bias section
+                           );
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/concepts/pmConceptsPhotcode.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/concepts/pmConceptsPhotcode.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/concepts/pmConceptsPhotcode.c	(revision 9533)
@@ -0,0 +1,36 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "pmConceptsPhotcode.h"
+
+char *pmConceptsPhotcodeForView (pmConfig *config, pmFPAfile *file, pmFPAview *view)
+{
+
+    if (view->chip < -1) {
+        psError(PS_ERR_IO, true, "photcodes undefined for FPA: defined by chip\n");
+        return NULL;
+    }
+
+    // select recipe options supplied on command line
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, "PHASE2");
+    if (recipe == NULL) {
+        psError(PS_ERR_IO, true, "recipe PHASE2 not found\n");
+        return NULL;
+    }
+
+    // select photcode rule from recipe
+    char *rule = psMetadataLookupStr (NULL, recipe, "PHOTCODE.RULE");
+    if (rule == NULL) {
+        psError(PS_ERR_IO, true, "PHOTCODE.RULE not found in PHASE2 recipe\n");
+        return NULL;
+    }
+
+    // convert rule to real photcode
+    char *photcode = pmFPAfileNameFromRule (rule, file, view);
+
+    return photcode;
+}
Index: /tags/RHL_v1_1/psModules/src/concepts/pmConceptsPhotcode.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/concepts/pmConceptsPhotcode.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/concepts/pmConceptsPhotcode.h	(revision 9533)
@@ -0,0 +1,11 @@
+#ifndef PM_CONCEPTS_PHOTCODE_H
+#define PM_CONCEPTS_PHOTCODE_H
+
+#include "pslib.h"
+#include "pmConfig.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+char *pmConceptsPhotcodeForView (pmConfig *config, pmFPAfile *file, pmFPAview *view);
+
+# endif
Index: /tags/RHL_v1_1/psModules/src/concepts/pmConceptsRead.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/concepts/pmConceptsRead.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/concepts/pmConceptsRead.c	(revision 9533)
@@ -0,0 +1,491 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmFPA.h"
+#include "pmHDU.h"
+#include "pmHDUUtils.h"
+#include "pmConcepts.h"
+#include "pmConceptsRead.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// This function gets called for the really boring concepts --- where all you have to do is parse from a
+// header or database and you don't need to muck around with conversions.  There is no similar "formatPlain",
+// since the type is already known.
+static psMetadataItem *parsePlain(psMetadataItem *concept, // The concept to parse
+                                  psMetadataItem *pattern // The concept pattern
+                                 )
+{
+    assert(concept);
+    assert(pattern);
+
+    switch (pattern->type) {
+    case PS_DATA_STRING: {
+            psString string = psMetadataItemParseString(concept); // Get the string, so I can free it after it
+            // goes on the MetadataItem
+            psMetadataItem *item = psMetadataItemAllocStr(pattern->name, pattern->comment, string);
+            psFree(string);
+            return item;
+        }
+    case PS_DATA_S32:
+        return psMetadataItemAllocS32(pattern->name, pattern->comment, psMetadataItemParseS32(concept));
+    case PS_DATA_F32:
+        return psMetadataItemAllocF32(pattern->name, pattern->comment, psMetadataItemParseF32(concept));
+    case PS_DATA_F64:
+        return psMetadataItemAllocF64(pattern->name, pattern->comment, psMetadataItemParseF64(concept));
+    default:
+        psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) is not of a standard type (%x)\n",
+                 pattern->name, pattern->comment, pattern->type);
+        return NULL;
+    }
+}
+
+
+// Parse a single concept
+static bool conceptParse(pmConceptSpec *spec, // The concept specification
+                         psMetadataItem *concept, // The concept to parse
+                         psMetadata *cameraFormat, // The camera format
+                         psMetadata *target, // The target
+                         pmFPA *fpa,    // The FPA
+                         pmChip *chip,  // The chip
+                         pmCell *cell   // The cell
+                        )
+{
+    assert(spec);
+    assert(cameraFormat);
+    assert(target);
+
+    if (!concept) {
+        psError(PS_ERR_UNKNOWN, true, "Concept is NULL");
+        return false;
+    }
+
+    psMetadataItem *parsed = NULL;  // The parsed concept
+    if (spec->parse) {
+        parsed = spec->parse(concept, spec->blank, cameraFormat, fpa, chip, cell);
+    } else {
+        parsed = parsePlain(concept, spec->blank);
+    }
+    if (!parsed) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to parse concept %s\n", spec->blank->name);
+        return false;
+    }
+
+    // Plug the parsed concept into a new psMetadataItem, so each "concept" has its own version that can
+    // be altered without affecting the others.  Also, so that we maintain the template name and comment.
+    psMetadataItem *cleaned = NULL;     // Item that's been cleaned up --- correct name and comment
+    switch (spec->blank->type) {
+    case PS_DATA_STRING:
+        cleaned = psMetadataItemAllocStr(spec->blank->name, spec->blank->comment, parsed->data.V);
+        break;
+    case PS_DATA_S32:
+        cleaned = psMetadataItemAllocS32(spec->blank->name, spec->blank->comment, parsed->data.S32);
+        break;
+    case PS_DATA_F32:
+        cleaned = psMetadataItemAllocF32(spec->blank->name, spec->blank->comment, parsed->data.F32);
+        break;
+    case PS_DATA_F64:
+        cleaned = psMetadataItemAllocF64(spec->blank->name, spec->blank->comment, parsed->data.F64);
+        break;
+    default:
+        cleaned = psMetadataItemAlloc(spec->blank->name, parsed->type, spec->blank->comment,
+                                      parsed->data.V);
+    }
+    psFree(parsed);
+    psMetadataAddItem(target, cleaned, PS_LIST_TAIL, PS_META_REPLACE);
+    psFree(cleaned);                 // Drop reference
+    return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmConceptsReadFromCamera(psMetadata *specs, // The concept specifications
+                              pmCell *cell,  // The cell
+                              psMetadata *target // Place into which to read the concepts
+                             )
+{
+    PS_ASSERT_PTR_NON_NULL(specs, false);
+    PS_ASSERT_PTR_NON_NULL(target, false);
+    if (!cell) {
+        psError(PS_ERR_UNKNOWN, true, "cell is NULL");
+        return false;
+    }
+
+    pmHDU *hdu = pmHDUGetLowest(NULL, NULL, cell); // The HDU at the lowest level
+    if (!hdu) {
+        psError(PS_ERR_UNKNOWN, true, "Can't find HDU for cell");
+        return false;
+    }
+    psMetadata *cameraFormat = hdu->format; // The camera format
+    psMetadata *cellConfig = cell->config; // The camera configuration for this cell
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    bool status = true;                 // Status of reading concepts
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psString name = specItem->name; // The concept name
+        psMetadataItem *conceptItem = psMetadataLookup(cellConfig, name); // The concept, or NULL
+        if (conceptItem) {
+            if (conceptItem->type == PS_DATA_STRING) {
+                // Check the SOURCE
+                psString nameSource = NULL; // String with the concept name and ".SOURCE" added
+                psStringAppend(&nameSource, "%s.SOURCE", name);
+                bool mdok = true;       // Status of MD lookup
+                psString source = psMetadataLookupStr(&mdok, cell->config, nameSource); // The source
+                psFree(nameSource);
+                if (mdok && strlen(source) > 0 && strcasecmp(source, "VALUE") == 0) {
+                    if (!conceptParse(spec, conceptItem, cameraFormat, target, NULL, NULL, cell)) {
+                        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to parse concept %s from camera "
+                                "configuration\n", name);
+                        status = false;
+                    }
+                } else if (source && (strlen(source) == 0 || strcasecmp(source, "HEADER") != 0)) {
+                    // We leave "HEADER" to pmConceptsReadFromHeader
+                    psError(PS_ERR_IO, true, "%s isn't HEADER or VALUE --- can't read %s\n", source,
+                            name);
+                    continue;
+                }
+            } else {
+                // Another type --- should be OK
+                if (!conceptParse(spec, conceptItem, cameraFormat, target, NULL, NULL, cell)) {
+                    psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to parse concept %s from camera "
+                            "configuration.\n", name);
+                    status = false;
+                }
+            }
+        }
+    }
+    psFree(specsIter);
+    return status;
+}
+
+
+bool pmConceptsReadFromDefaults(psMetadata *specs, // The concept specifications
+                                pmFPA *fpa, // The FPA
+                                pmChip *chip, // The chip
+                                pmCell *cell, // The cell
+                                psMetadata *target // Place into which to read the concepts
+                               )
+{
+    PS_ASSERT_PTR_NON_NULL(specs, false);
+    PS_ASSERT_PTR_NON_NULL(target, false);
+
+    psTrace("psModules.concepts", 3, "Reading concepts from defaults...\n");
+
+    pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
+    if (!hdu) {
+        // We read the defaults for all the HDUs we could find
+        return true;
+    }
+    psMetadata *cameraFormat = hdu->format; // The camera format
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *defaults = psMetadataLookupMD(&mdok, cameraFormat, "DEFAULTS"); // The DEFAULTS spec
+    if (!mdok || !defaults) {
+        psError(PS_ERR_IO, true, "Failed to find \"DEFAULTS\"");
+        return false;
+    }
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    bool status = true;                 // Status of reading concepts
+    psErrorClear();   // we're going to declare all errors "old" => won't clear stack
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psString name = specItem->name; // The concept name
+        psMetadataItem *conceptItem = psMetadataLookup(defaults, name); // The concept, or NULL
+        psTrace("psModules.concepts", 10, "%s: %p\n", name, conceptItem);
+        if (conceptItem && conceptItem->type == PS_DATA_METADATA) {
+            psTrace("psModules.concepts", 5, "%s is of type METADATA.\n", name);
+            // Check for DEPEND
+            psMetadata *dependMenu = conceptItem->data.V; // The DEPEND menu
+            psString depend = NULL; // The CONCEPT.DEPEND
+            psStringAppend(&depend, "%s.DEPEND", name);
+            const char *dependConcept = psMetadataLookupStr(&mdok, defaults, depend); // The concept name
+            if (!mdok || !dependConcept || strlen(dependConcept) == 0) {
+                psError(PS_ERR_IO, true, "Unable to parse %s: couldn't find %s in DEFAULTS.\n", name,
+                        depend);
+                psFree(depend);
+                continue;
+            }
+            psFree(depend);
+            // Now look up the depend value
+            psMetadataItem *dependValue = NULL; // The value of the concept we're looking up
+            if (cell) {
+                dependValue = psMetadataLookup(cell->concepts, dependConcept);
+            }
+            if (chip && !dependValue) {
+                dependValue = psMetadataLookup(chip->concepts, dependConcept);
+            }
+            if (fpa && !dependValue) {
+                dependValue = psMetadataLookup(chip->concepts, dependConcept);
+            }
+            if (!dependValue) {
+                psError(PS_ERR_IO, true, "Unable to find %s to resolve %s in DEFAULTS.\n",
+                        dependConcept, name);
+                continue;
+            }
+            if (dependValue->type != PS_DATA_STRING) {
+                psError(PS_ERR_BAD_PARAMETER_TYPE, true, "%s is required to resolve %s in DEFAULTS, "
+                        "but it is not of type STRING.\n", dependConcept, name);
+                continue;
+            }
+            const char *dependKey = dependValue->data.V; // The key to the DEPEND menu
+            psTrace("psModules.concepts", 7, "%s.DEPEND resolves to %s....\n", name, dependKey);
+
+            conceptItem = psMetadataLookup(dependMenu, dependKey);
+            // Now we can parse this as we would ordinarily
+        }
+        if (conceptItem && !conceptParse(spec, conceptItem, cameraFormat, target, fpa, chip, cell)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to parse concept %s from DEFAULTS.\n", name);
+            status = false;
+        }
+    }
+    psFree(specsIter);
+    return status;
+}
+
+
+bool pmConceptsReadFromHeader(psMetadata *specs, // The concept specifications
+                              pmFPA *fpa, // The FPA
+                              pmChip *chip, // The chip
+                              pmCell *cell,  // The cell
+                              psMetadata *target // Place into which to read the concepts
+                             )
+{
+    PS_ASSERT_PTR_NON_NULL(specs, false);
+    PS_ASSERT_PTR_NON_NULL(target, false);
+
+    pmHDU *hduLow = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
+    if (!hduLow) {
+        psError(PS_ERR_UNKNOWN, true, "Can't find HDU at lowest level");
+        return false;
+    }
+    pmHDU *hduHigh = pmHDUGetHighest(fpa, chip, cell); // The HDU at the highest level
+    if (!hduHigh) {
+        psError(PS_ERR_UNKNOWN, true, "Can't find HDU at the highest level");
+        return false;
+    }
+    assert(hduLow->format == hduHigh->format); // Just in case....
+    psMetadata *cameraFormat = hduLow->format; // The camera format
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *transSpec = psMetadataLookupMD(&mdok, cameraFormat, "TRANSLATION"); // The TRANSLATION spec
+    if (!mdok || !transSpec) {
+        psError(PS_ERR_IO, true, "Failed to find \"TRANSLATION\"");
+        return false;
+    }
+
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    bool status = true;                 // Status of reading concepts
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psString name = specItem->name; // The concept name
+        psMetadataItem *headerItem = NULL; // The value of the concept from the header
+        // First check the cell configuration
+        if (cell && cell->config) {
+            psMetadataItem *conceptItem = psMetadataLookup(cell->config, name); // The concept, or NULL
+            if (conceptItem) {
+                // Check the SOURCE
+                psString nameSource = NULL; // String with the concept name and ".SOURCE" added
+                psStringAppend(&nameSource, "%s.SOURCE", name);
+                psString source = psMetadataLookupStr(&mdok, cell->config, nameSource); // The source
+                psFree(nameSource);
+                if (mdok && strlen(source) && strcasecmp(source, "HEADER") == 0) {
+                    headerItem = psMetadataLookup(hduLow->header, conceptItem->data.V);
+                    if (!headerItem && hduHigh != hduLow) {
+                        headerItem = psMetadataLookup(hduHigh->header, conceptItem->data.V);
+                    }
+                    psMemIncrRefCounter(headerItem);
+                }
+                // Leave the error handling to pmConceptsFromCamera, which should already have been called
+            }
+        }
+        if (! headerItem) {
+            psString keywords = psMetadataLookupStr(&mdok, transSpec, name); // The FITS keywords
+            if (mdok && strlen(keywords) > 0) {
+                // In case there are multiple headers
+                psList *keys = psStringSplit(keywords, " ,;", true); // List of keywords
+                if (keys->n == 1) {
+                    // Only one key --- proceed as usual
+                    headerItem = psMetadataLookup(hduLow->header, keywords);
+                    if (!headerItem && hduHigh != hduLow) {
+                        headerItem = psMetadataLookup(hduHigh->header, keywords);
+                    }
+                    psMemIncrRefCounter(headerItem);
+                } else {
+                    psListIterator *keysIter = psListIteratorAlloc(keys, PS_LIST_HEAD, false); // Iterator
+                    psString key = NULL; // Item from iteration
+                    psList *values = psListAlloc(NULL); // List containing the values
+                    while ((key = psListGetAndIncrement(keysIter))) {
+                        psMetadataItem *value = psMetadataLookup(hduLow->header, key);
+                        if (!value && hduHigh != hduLow) {
+                            value = psMetadataLookup(hduHigh->header, key);
+                        }
+                        if (!value) {
+                            psWarning("Unable to find header %s --- assuming value is NULL", key);
+                        }
+                        psListAdd(values, PS_LIST_TAIL, value);
+                    }
+                    psFree(keysIter);
+                    headerItem = psMetadataItemAlloc(name, PS_DATA_LIST, specItem->comment, values);
+                    psFree(values);
+                }
+                psFree(keys);
+            }
+        }
+
+        // This will also clean up the name
+        if (headerItem && !conceptParse(spec, headerItem, cameraFormat, target, fpa, chip, cell)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to parse concept %s from header.\n", name);
+            status = false;
+        }
+        psFree(headerItem);
+    }
+    psFree(specsIter);
+    return status;
+}
+
+
+// XXX --- the below code has NOT been tested!
+bool pmConceptsReadFromDatabase(psMetadata *specs, // The concept specifications
+                                pmFPA *fpa, // The FPA
+                                pmChip *chip, // The chip
+                                pmCell *cell,  // The cell
+                                psDB *db, // The database handle
+                                psMetadata *target // Place into which to read the concepts
+                               )
+{
+    PS_ASSERT_PTR_NON_NULL(specs, false);
+    PS_ASSERT_PTR_NON_NULL(target, false);
+
+    #ifdef OMIT_PSDB
+
+    return false;
+    #else
+
+    pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
+    if (!hdu) {
+        // We read the database for all the HDUs we could find
+        return true;
+    }
+    psMetadata *cameraFormat = hdu->format; // The camera format
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *dbSpec = psMetadataLookupMD(&mdok, cameraFormat, "DATABASE"); // The DATABASE spec
+    if (!mdok || !dbSpec) {
+        psError(PS_ERR_IO, true, "Unable to find DATABASE in camera configuration.\n");
+        return false;
+    }
+
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    bool status = true;                 // Status of reading concepts
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psString name = specItem->name; // The concept name
+
+        psMetadata *dbLookup = psMetadataLookupMD(&mdok, dbSpec, name);
+        if (mdok && dbLookup) {
+            const char *tableName = psMetadataLookupStr(&mdok, dbLookup, "TABLE"); // Table name
+            // Names of the "where" columns
+            const char *givenCols = psMetadataLookupStr(&mdok, dbLookup, "GIVENDBCOL");
+            // Values of the "where" columns
+            const char *givenPS = psMetadataLookupStr(&mdok, dbLookup, "GIVENPS");
+
+            // Now, need to get the "given"s
+            if (strlen(givenCols) > 0 || strlen(givenPS) > 0) {
+                psList *cols = psStringSplit(givenCols, ",;", true); // List of column names
+                psList *values = psStringSplit(givenPS, ",;", true); // List of value names for the columns
+                psMetadata *selection = psMetadataAlloc(); // The stuff to select in the DB
+                if (cols->n != values->n) {
+                    psLogMsg(__func__, PS_LOG_WARN,
+                             "The GIVENDBCOL and GIVENPS entries for %s do not have "
+                             "the same number of entries --- ignored.\n", name);
+                } else {
+                    // Iterators for the lists
+                    psListIterator *colsIter = psListIteratorAlloc(cols, PS_LIST_HEAD, false);
+                    psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false);
+                    char *column = NULL;    // Name of the column
+                    while ((column = psListGetAndIncrement(colsIter))) {
+                        char *dependName = psListGetAndIncrement(valuesIter); // Name for the value
+                        if (!strlen(column) || !strlen(name)) {
+                            psLogMsg(__func__, PS_LOG_WARN, "One of the columns or value names for %s is "
+                                     " empty --- ignored.\n", name);
+                        } else {
+                            // Search for the value name
+                            psMetadataItem *item = NULL; // The value
+                            if (!item && cell) {
+                                item = psMetadataLookup(cell->concepts, dependName);
+                            }
+                            if (!item && chip) {
+                                item = psMetadataLookup(chip->concepts, dependName);
+                            }
+                            if (!item && fpa) {
+                                item = psMetadataLookup(fpa->concepts, dependName);
+                            }
+                            if (! item) {
+                                psLogMsg(__func__, PS_LOG_ERROR, "Unable to find the value name %s for DB"
+                                         " lookup on %s --- ignored.\n", dependName, name);
+                            } else {
+                                // We need to create a new psMetadataItem.  I don't think we can't
+                                // simply hack the existing one, since that could conceivably cause
+                                // memory leaks
+                                psMetadataItem *newItem = psMetadataItemAlloc(name, item->type,
+                                                          item->comment,
+                                                          item->data.V);
+                                psMetadataAddItem(selection, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+                                psFree(newItem);
+                            }
+                        }
+                        psFree(dependName);
+                        psFree(column);
+                    } // Iterating through the columns
+                    psFree(colsIter);
+                    psFree(valuesIter);
+
+                    psArray *dbResult = psDBSelectRows(db, tableName, selection, 2); // Lookup result
+                    // Note that we use limit=2 in order to test if there are multiple rows returned
+
+                    psMetadataItem *conceptItem = NULL; // The final result of the DB lookup
+                    if (dbResult->n == 0) {
+                        psLogMsg(__func__, PS_LOG_WARN,
+                                 "Unable to find any rows in DB for %s --- ignored\n", name);
+                    } else {
+                        if (dbResult-> n > 1) {
+                            psLogMsg(__func__, PS_LOG_WARN,
+                                     "Multiple rows returned in DB lookup for %s --- "
+                                     " using the first one only.\n", name);
+                        }
+                        conceptItem = (psMetadataItem*)dbResult->data[0];
+                    }
+
+                    // Now we have the result
+                    if (!conceptParse(spec, conceptItem, cameraFormat, target, fpa, chip, cell)) {
+                        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to parse concept %s from database.\n",
+                                name);
+                        status = false;
+                    }
+
+                }
+                psFree(cols);
+                psFree(values);
+            }
+        } // Doing the "given"s.
+
+    } // Iterating through the concept specifications
+    psFree(specsIter);
+
+    return status;
+    #endif
+}
+
+
Index: /tags/RHL_v1_1/psModules/src/concepts/pmConceptsRead.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/concepts/pmConceptsRead.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/concepts/pmConceptsRead.h	(revision 9533)
@@ -0,0 +1,94 @@
+#ifndef PM_CONCEPTS_READ_H
+#define PM_CONCEPTS_READ_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+bool pmConceptsReadFromCamera(psMetadata *specs, // The concept specifications
+                              pmCell *cell,  // The cell
+                              psMetadata *target // Place into which to read the concepts
+                             );
+bool pmConceptsReadFromDefaults(psMetadata *specs, // The concept specifications
+                                pmFPA *fpa, // The FPA
+                                pmChip *chip, // The chip
+                                pmCell *cell, // The cell
+                                psMetadata *target // Place into which to read the concepts
+                               );
+bool pmConceptsReadFromHeader(psMetadata *specs, // The concept specifications
+                              pmFPA *fpa, // The FPA
+                              pmChip *chip, // The chip
+                              pmCell *cell,  // The cell
+                              psMetadata *target // Place into which to read the concepts
+                             );
+bool pmConceptsReadFromDatabase(psMetadata *specs, // The concept specifications
+                                pmFPA *fpa, // The FPA
+                                pmChip *chip, // The chip
+                                pmCell *cell,  // The cell
+                                psDB *db, // The database handle
+                                psMetadata *target // Place into which to read the concepts
+                               );
+
+
+
+
+#ifdef OLD
+psMetadataItem *pmConceptReadFromCamera(pmCell *cell, // The cell
+                                        const char *concept // Name of concept
+                                       );
+
+psMetadataItem *pmConceptReadFromHeader(pmFPA *fpa, // The FPA that contains the chip
+                                        pmChip *chip, // The chip that contains the cell
+                                        pmCell *cell, // The cell
+                                        const char *concept // Name of concept
+                                       );
+
+psMetadataItem *pmConceptReadFromDefault(pmFPA *fpa, // The FPA that contains the chip
+        pmChip *chip, // The chip that contains the cell
+        pmCell *cell, // The cell
+        const char *concept // Name of concept
+                                        );
+
+psMetadataItem *pmConceptReadFromDB(pmFPA *fpa, // The FPA that contains the chip
+                                    pmChip *chip, // The chip that contains the cell
+                                    pmCell *cell, // The cell
+                                    psDB *db, // DB handle
+                                    const char *concept // Name of concept
+                                   );
+
+psMetadataItem *pmConceptRead(pmFPA *fpa, // The FPA
+                              pmChip *chip,// The chip
+                              pmCell *cell, // The cell
+                              psDB *db, // DB handle
+                              const char *concept // Concept name
+                             );
+
+float pmConceptReadF32(pmFPA *fpa,        // The FPA
+                       pmChip *chip,      // The chip
+                       pmCell *cell,      // The cell
+                       psDB *db,          // DB handle
+                       const char *name // Name of the concept
+                      );
+
+double pmConceptReadF64(pmFPA *fpa,   // The FPA
+                        pmChip *chip, // The chip
+                        pmCell *cell, // The cell
+                        psDB *db,     // DB handle
+                        const char *name // Name of the concept
+                       );
+
+int pmConceptReadS32(pmFPA *fpa,   // The FPA
+                     pmChip *chip, // The chip
+                     pmCell *cell, // The cell
+                     psDB *db,     // DB handle
+                     const char *name // Name of the concept
+                    );
+
+psString pmConceptReadString(pmFPA *fpa, // The FPA
+                             pmChip *chip, // The chip
+                             pmCell *cell, // The cell
+                             psDB *db,  // DB handle
+                             const char *name // Name of the concept
+                            );
+#endif
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/concepts/pmConceptsStandard.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/concepts/pmConceptsStandard.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/concepts/pmConceptsStandard.c	(revision 9533)
@@ -0,0 +1,827 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+#include "pslib.h"
+
+#include "pmFPA.h"
+#include "pmConceptsRead.h"
+#include "pmConceptsWrite.h"
+#include "pmConceptsStandard.h"
+
+// The functions in this file are intended to be called solely within the psModules concepts code.  For this
+// reason, they use "assert" instead of the PS_ASSERT_WHATEVER functions --- if there's a problem, then
+// there's a BIG problem that affects all of the code.
+
+#define COMPARE_REGIONS(a,b) (((a)->x0 == (b)->x0 && \
+                               (a)->x1 == (b)->x1 && \
+                               (a)->y0 == (b)->y0 && \
+                               (a)->y1 == (b)->y1) ? true : false)
+
+#define TYPE_CASE(assign, item, TYPE) \
+case PS_TYPE_##TYPE: \
+assign = item->data.TYPE; \
+break;
+
+
+
+static double defaultCoordScaling(psMetadataItem *pattern)
+{
+    if (strcmp(pattern->name, "FPA.RA") == 0) {
+        psLogMsg(__func__, PS_LOG_WARN, "Assuming format for %s is HOURS.\n", pattern->name);
+        return M_PI / 12.0;
+    }
+    if (strcmp(pattern->name, "FPA.DEC") == 0) {
+        psLogMsg(__func__, PS_LOG_WARN, "Assuming format for %s is DEGREES.\n", pattern->name);
+        return M_PI / 180.0;
+    }
+    psAbort(__func__, "Should never ever get here.\n");
+    return NAN;
+}
+
+
+// FPA.RA and FPA.DEC
+psMetadataItem *p_pmConceptParse_FPA_Coords(psMetadataItem *concept,
+        psMetadataItem *pattern,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell)
+{
+    assert(concept);
+    assert(pattern);
+    assert(cameraFormat);
+
+    double coords = NAN;                // The coordinates
+    switch (concept->type) {
+    case PS_TYPE_F32:
+        coords = concept->data.F32;
+        break;
+    case PS_TYPE_F64:
+        coords = concept->data.F64;
+        break;
+    case PS_DATA_STRING:
+        // Sexagesimal format
+        {
+            int big, medium;
+            float small;
+            // XXX: Upgrade path is to allow dd:mm.mmm
+            if (sscanf(concept->data.V, "%d:%d:%f", &big, &medium, &small) != 3 &&
+                    sscanf(concept->data.V, "%d %d %f", &big, &medium, &small) != 3)
+            {
+                psError(PS_ERR_UNKNOWN, true, "Cannot interpret FPA.RA: %s\n", concept->data.str);
+                break;
+            }
+            coords = abs(big) + (float)medium/60.0 + small/3600.0;
+            if (big < 0)
+            {
+                coords *= -1.0;
+            }
+        }
+        break;
+    default:
+        psError(PS_ERR_UNKNOWN, true, "%s concept is of an unexpected type: %x\n", pattern->name, concept->type);
+        return NULL;
+    }
+
+    // How to interpret the coordinates
+    bool mdok = true;           // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMD(&mdok, cameraFormat, "FORMATS");
+    if (mdok && formats) {
+        psString format = psMetadataLookupStr(&mdok, formats, pattern->name);
+        if (mdok && strlen(format) > 0) {
+            if (strcasecmp(format, "HOURS") == 0) {
+                coords *= M_PI / 12.0;
+            } else if (strcasecmp(format, "DEGREES") == 0) {
+                coords *= M_PI / 180.0;
+            } else if (strcasecmp(format, "RADIANS") == 0) {
+                // No action required
+            } else {
+                coords *= defaultCoordScaling(pattern);
+            }
+        } else {
+            coords *= defaultCoordScaling(pattern);
+        }
+    } else {
+        coords *= defaultCoordScaling(pattern);
+    }
+
+    return psMetadataItemAllocF64(pattern->name, pattern->comment, coords);
+}
+
+// FPA.RA and FPA.DEC
+psMetadataItem *p_pmConceptFormat_FPA_Coords(psMetadataItem *concept,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell)
+{
+    assert(concept);
+    assert(cameraFormat);
+
+    double coords = concept->data.F64;  // The coordinates
+
+    // How to interpret the coordinates
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMD(&mdok,
+                          cameraFormat,
+                          "FORMATS");
+    if (mdok && formats) {
+        psString format = psMetadataLookupStr(&mdok,formats, concept->name);
+        if (mdok && strlen(format) > 0) {
+            if (strcasecmp(format, "HOURS") == 0) {
+                coords /= M_PI / 12.0;
+            } else if (strcasecmp(format, "DEGREES") == 0) {
+                coords /= M_PI / 180.0;
+            } else if (strcasecmp(format, "RADIANS") == 0) {
+                // No action required
+            } else {
+                coords /= defaultCoordScaling(concept);
+            }
+        } else {
+            coords /= defaultCoordScaling(concept);
+        }
+    } else {
+        coords /= defaultCoordScaling(concept);
+    }
+
+    // We choose to write sexagesimal format
+    int big, medium;
+    float small;
+    big = (int)coords;
+    medium = (int)(60.0*(coords - (double)big));
+    small = 3600.0*(coords - (double)big - 60.0 * (double)medium);
+    psString coordString = NULL;        // String with the coordinates in sexagesimal format
+    psStringAppend(&coordString, "%d:%d:%.2f", big, medium, small);
+    psMetadataItem *coordItem = psMetadataItemAllocStr(concept->name, concept->comment, coordString);
+    psFree(coordString);
+
+    return coordItem;
+}
+
+
+psMetadataItem *p_pmConceptParse_CELL_TRIMSEC(psMetadataItem *concept,
+        psMetadataItem *pattern,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell)
+{
+    assert(concept);
+    assert(cell);
+    assert(pattern);
+
+    psRegion *trimsec = psRegionAlloc(0, 0, 0, 0);
+
+    if (concept->type != PS_DATA_STRING) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.TRIMSEC after read is not of type STR (%x)\n", concept->type);
+    } else {
+        *trimsec = psRegionFromString(concept->data.V);
+    }
+
+    psMetadataItem *item = psMetadataItemAllocPtr(pattern->name, PS_DATA_REGION, pattern->comment, trimsec);
+    psFree(trimsec);
+    return item;
+}
+
+psMetadataItem *p_pmConceptParse_CELL_BIASSEC(psMetadataItem *concept,
+        psMetadataItem *pattern,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell)
+{
+    assert(concept);
+    assert(cell);
+    assert(pattern);
+
+    psList *biassecs = psListAlloc(NULL); // List of bias sections
+
+    switch (concept->type) {
+    case PS_DATA_STRING: {
+            psList *regions = psStringSplit(concept->data.V, " ;", true); // List of regions
+            psListIterator *regionsIter = psListIteratorAlloc(regions, PS_LIST_HEAD, false); // Iterator
+            psString regionString = NULL; // Region string from iteration
+            while ((regionString = psListGetAndIncrement(regionsIter))) {
+                psRegion *region = psAlloc(sizeof(psRegion)); // The region
+                *region = psRegionFromString(regionString);
+                psListAdd(biassecs, PS_LIST_TAIL, region);
+                psFree(region);           // Drop reference
+            }
+            psFree(regionsIter);
+            psFree(regions);
+            break;
+        }
+    case PS_DATA_LIST: {
+            psList *regions = concept->data.V; // The list of regions
+            psListIterator *regionsIter = psListIteratorAlloc(regions, PS_LIST_HEAD, false); // Iterator
+            psMetadataItem *regionItem = NULL; // Item from list iteration
+            while ((regionItem = psListGetAndIncrement(regionsIter))) {
+                if (regionItem->type != PS_DATA_STRING) {
+                    psLogMsg(__func__, PS_LOG_WARN, "CELL.BIASSEC member is not of type STR --- ignored.\n");
+                    continue;
+                }
+                psRegion *region = psAlloc(sizeof(psRegion)); // The region
+                *region = psRegionFromString(regionItem->data.V);
+                psListAdd(biassecs, PS_LIST_TAIL, region);
+                psFree(region);           // Drop reference
+            }
+            psFree(regionsIter);
+            break;
+        }
+    default:
+        psError(PS_ERR_UNKNOWN, true, "CELL.BIASSEC after read is not of type STRING or LIST --- assuming "
+                "blank.\n");
+    }
+
+    psMetadataItem *item = psMetadataItemAllocPtr(pattern->name, PS_DATA_LIST, pattern->comment, biassecs);
+    psFree(biassecs);               // Drop reference
+    return item;
+}
+
+// CELL.XBIN and CELL.YBIN
+psMetadataItem *p_pmConceptParse_CELL_Binning(psMetadataItem *concept,
+        psMetadataItem *pattern,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell)
+{
+    assert(concept);
+    assert(pattern);
+
+    int binning = 1;                    // Binning factor in x
+    switch (concept->type) {
+    case PS_DATA_STRING: {
+            psString binString = concept->data.V; // The string containing the binning
+            if ((strcmp(pattern->name, "CELL.XBIN") == 0 && sscanf(binString, "%d %*d", &binning) != 1 &&
+                    sscanf(binString, "%d,%*d", &binning) != 1) ||
+                    (strcmp(pattern->name, "CELL.YBIN") == 0 && sscanf(binString, "%*d %d", &binning) != 1 &&
+                     sscanf(binString, "%*d,%d", &binning) != 1)) {
+                psError(PS_ERR_UNKNOWN, true, "Unable to parse string to get %s: %s\n", pattern->name, binString);
+            }
+            break;
+        }
+        TYPE_CASE(binning, concept, U8);
+        TYPE_CASE(binning, concept, U16);
+        TYPE_CASE(binning, concept, U32);
+        TYPE_CASE(binning, concept, S8);
+        TYPE_CASE(binning, concept, S16);
+        TYPE_CASE(binning, concept, S32);
+    default:
+        psError(PS_ERR_UNKNOWN, true, "Note sure how to parse %s of type %x --- assuming 1.\n", pattern->name,
+                concept->type);
+    }
+
+    return psMetadataItemAllocS32(pattern->name, pattern->comment, binning);
+}
+
+
+psMetadataItem *p_pmConceptParse_TIMESYS(psMetadataItem *concept,
+        psMetadataItem *pattern,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell)
+{
+    assert(concept);
+    assert(pattern);
+
+    psTimeType timeSys = PS_TIME_UTC;   // The time system
+    psString sys = concept->data.V;     // The time system string
+    if (concept->type != PS_DATA_STRING || strlen(sys) <= 0) {
+        psError(PS_ERR_UNKNOWN, true, "Can't interpret %s --- assuming UTC.\n", pattern->name);
+    } else if (strcasecmp(sys, "TAI") == 0) {
+        timeSys = PS_TIME_TAI;
+    } else if (strcasecmp(sys, "UTC") == 0) {
+        timeSys = PS_TIME_UTC;
+    } else if (strcasecmp(sys, "UT1") == 0) {
+        timeSys = PS_TIME_UT1;
+    } else if (strcasecmp(sys, "TT") == 0) {
+        timeSys = PS_TIME_TT;
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "Can't interpret %s --- assuming UTC.\n", pattern->name);
+    }
+
+    return psMetadataItemAllocS32(pattern->name, pattern->comment, timeSys);
+}
+
+psMetadataItem *p_pmConceptParse_TIME(psMetadataItem *concept,
+                                      psMetadataItem *pattern,
+                                      psMetadata *cameraFormat,
+                                      pmFPA *fpa,
+                                      pmChip *chip,
+                                      pmCell *cell)
+{
+    assert(concept);
+    assert(cameraFormat);
+
+    // Need TIMESYS first
+    psString timesysName = psStringCopy(pattern->name); // e.g., "CELL.TIME" --> "CELL.TIMESYS"
+    timesysName = psStringSubstitute(timesysName, "TIMESYS", "TIME");
+    bool mdok = false;                  // Result of MD lookup
+    psTimeType timeSys;                 // The time system
+    if (cell) {
+        timeSys = psMetadataLookupS32(&mdok, cell->concepts, timesysName);
+    }
+    if (!mdok && chip) {
+        timeSys = psMetadataLookupS32(&mdok, chip->concepts, timesysName);
+    }
+    if (!mdok && fpa) {
+        timeSys = psMetadataLookupS32(&mdok, fpa->concepts, timesysName);
+    }
+    if (!mdok) {
+        psLogMsg(__func__, PS_LOG_WARN, "Unable to find %s in concepts when parsing %s --- assuming UTC.\n",
+                 timesysName, pattern->name);
+        timeSys = PS_TIME_UTC;
+    }
+    psFree(timesysName);
+
+    // Work out how the time is represented
+    bool usaTime = false;               // Is the time specified in USA (MM-DD-YYYY) order?
+    bool backwardsTime = false;         // Is the time specified in backwards (DD-MM-YYYY) order?
+    bool pre2000Time = false;           // Is the time specified pre-2000 (where the year only has two digits)
+    bool mjdTime = false;               // Is the time specified a MJD?
+    bool jdTime = false;                // Is the time specified a JD?
+
+    // Get format
+    psMetadata *formats = psMetadataLookupMD(&mdok, cameraFormat, "FORMATS");
+    if (!mdok || !formats) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find FORMATS in camera configuration.\n");
+        return NULL;
+    }
+
+    psString timeFormat = psMetadataLookupStr(&mdok, formats, pattern->name);
+    if (!mdok || strlen(timeFormat) == 0) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to find %s in FORMATS.\n", pattern->name);
+        return NULL;
+    }
+
+    // Parse the time format
+    psList *timeFormats = psStringSplit(timeFormat, " ,;", false); // List of the format options
+    psListIterator *timeFormatsIter = psListIteratorAlloc(timeFormats, PS_LIST_HEAD, false); // Iter
+    while ((timeFormat = psListGetAndIncrement(timeFormatsIter))) {
+        if (strcasecmp(timeFormat, "USA") == 0) {
+            usaTime = true;
+            backwardsTime = false;
+            jdTime = false;
+            mjdTime = false;
+        } else if (strcasecmp(timeFormat, "BACKWARDS") == 0) {
+            backwardsTime = true;
+            usaTime = false;
+            jdTime = false;
+            mjdTime = false;
+        } else if (strcasecmp(timeFormat, "PRE2000") == 0) {
+            pre2000Time = true;
+        } else if (strcasecmp(timeFormat, "MJD") == 0) {
+            mjdTime = true;
+            jdTime = false;
+            backwardsTime = false;
+            usaTime = false;
+        } else if (strcasecmp(timeFormat, "JD") == 0) {
+            jdTime = true;
+            mjdTime = false;
+            backwardsTime = false;
+            usaTime = false;
+        } else {
+            psError(PS_ERR_UNKNOWN, true, "Unrecognised FORMATS option for %s: %s --- "
+                    "ignored.\n", pattern->name, timeFormat);
+            psFree(timeFormatsIter);
+            psFree(timeFormats);
+            return NULL;
+        }
+    }
+    psFree(timeFormatsIter);
+    psFree(timeFormats);
+
+    psTime *time = NULL;                // The time
+    switch (concept->type) {
+    case PS_DATA_LIST: {
+            // The date and time are stored separately
+            // Assume the date is first and the time second
+            psList *dateTime = concept->data.V; // The list containing items for date and time
+            psMetadataItem *dateItem = psListGet(dateTime, PS_LIST_HEAD); // Item containing the date
+            psMetadataItem *timeItem = psListGet(dateTime, PS_LIST_HEAD + 1); // Item containing the time
+            if (dateItem->type != PS_DATA_STRING) {
+                psError(PS_ERR_UNKNOWN, true, "Date is not of type STR.\n");
+                return NULL;
+            }
+            psString dateString = dateItem->data.V; // The string with the date
+            int day = 0, month = 0, year = 0;
+            if (sscanf(dateString, "%d-%d-%d", &year, &month, &day) != 3 &&
+                    sscanf(dateString, "%d/%d/%d", &year, &month, &day) != 3) {
+                psError(PS_ERR_UNKNOWN, true, "Unable to read date: %s\n", dateString);
+                return NULL;
+            }
+            if (backwardsTime) {
+                // Need to switch days and years
+                int temp = day;
+                day = year;
+                year = temp;
+            }
+            if (usaTime) {
+                // Need to switch everything around.... Yanks!
+                int temp = day;
+                day = month;
+                month = year;
+                year = temp;
+            }
+            if (year < 100) {
+                if (pre2000Time) {
+                    year += 1900;
+                } else {
+                    year += 2000;
+                }
+            }
+            sprintf(dateString,"%04d-%02d-%02d", year, month, day);
+
+            psString timeString = NULL; // The string with the time
+            if (timeItem->type == PS_DATA_STRING) {
+                timeString = timeItem->data.V;
+            } else {
+                // Assume that time is specified in Second of Day (!)
+                double seconds = NAN;
+                switch (timeItem->type) {
+                    TYPE_CASE(seconds, timeItem, U8);
+                    TYPE_CASE(seconds, timeItem, U16);
+                    TYPE_CASE(seconds, timeItem, U32);
+                    TYPE_CASE(seconds, timeItem, S8);
+                    TYPE_CASE(seconds, timeItem, S16);
+                    TYPE_CASE(seconds, timeItem, S32);
+                    TYPE_CASE(seconds, timeItem, F32);
+                    TYPE_CASE(seconds, timeItem, F64);
+                default:
+                    psError(PS_ERR_UNKNOWN, true, "Time is not of an expected type: %x\n", timeItem->type);
+                    return NULL;
+                }
+                // Now print to timeString as "hh:mm:ss.ss"
+                int hours = seconds / 3600;
+                seconds -= (double)hours * 3600.0;
+                int minutes = seconds / 60;
+                seconds -= (double)minutes * 60.0;
+                psStringAppend(&timeString, "%02d:%02d:%02f", hours, minutes, seconds);
+            }
+            psString dateTimeString = NULL;
+            psStringAppend(&dateTimeString, "%sT%s", dateString, timeString);
+            time = psTimeFromISO(dateTimeString, timeSys);
+            psFree(dateTimeString);
+            break;
+        }
+    case PS_DATA_STRING: {
+            psString timeString = concept->data.V;   // String with the time
+            if (jdTime) {
+                double timeValue = strtod (timeString, NULL);
+                time = psTimeFromJD(timeValue);
+            } else if (mjdTime) {
+                double timeValue = strtod (timeString, NULL);
+                time = psTimeFromMJD(timeValue);
+            } else {
+                // It's ISO
+                time = psTimeFromISO(timeString, timeSys);
+            } // Interpreting the time string
+            break;
+        }
+    case PS_TYPE_F32: {
+            double timeValue = (double)concept->data.F32;
+            if (jdTime) {
+                time = psTimeFromJD(timeValue);
+            } else if (mjdTime) {
+                time = psTimeFromMJD(timeValue);
+            } else {
+                psError(PS_ERR_UNKNOWN, true, "Not sure how to parse %s (%f) --- trying JD\n",
+                        pattern->name, timeValue);
+                time = psTimeFromJD(timeValue);
+            }
+            break;
+        }
+    case PS_TYPE_F64: {
+            double timeValue = (double)concept->data.F64;
+            if (jdTime) {
+                time = psTimeFromJD(timeValue);
+            } else if (mjdTime) {
+                time = psTimeFromMJD(timeValue);
+            } else {
+                psError(PS_ERR_UNKNOWN, true, "Not sure how to parse %s (%f) --- trying JD\n",
+                        pattern->name, timeValue);
+                time = psTimeFromJD(timeValue);
+            }
+            break;
+        }
+    default:
+        psError(PS_ERR_UNKNOWN, true, "Unable to parse %s.\n", pattern->name);
+        return NULL;
+    }
+
+    psMetadataItem *item = psMetadataItemAllocPtr(pattern->name, PS_DATA_TIME, pattern->comment, time);
+    psFree(time);                       // Drop reference
+    return item;
+}
+
+// Correct a position --- in case the user wants FORTRAN indexing (like the FITS standard...)
+static int fortranCorr(psMetadata *cameraFormat, // The camera format description
+                       const char *name // Name of concept to check for FORTRAN indexing
+                      )
+{
+    bool mdok = false;                  // Result of MD lookup
+    psMetadata *formats = psMetadataLookupMD(&mdok, cameraFormat, "FORMATS");
+    if (mdok && formats) {
+        psString format = psMetadataLookupStr(&mdok, formats, name);
+        if (mdok && strlen(format) > 0 && strcasecmp(format, "FORTRAN") == 0) {
+            return 1;
+        }
+    }
+    return 0;
+}
+
+psMetadataItem *p_pmConceptParse_Positions(psMetadataItem *concept,
+        psMetadataItem *pattern,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell)
+{
+    assert(concept);
+    assert(cameraFormat);
+
+    int offset = 0;                     // Offset of component (0,0) corner from the parent (0,0) corner
+
+    switch (concept->type) {
+        TYPE_CASE(offset, concept, U8);
+        TYPE_CASE(offset, concept, U16);
+        TYPE_CASE(offset, concept, U32);
+        TYPE_CASE(offset, concept, S8);
+        TYPE_CASE(offset, concept, S16);
+        TYPE_CASE(offset, concept, S32);
+    default:
+        if (concept->type == PS_TYPE_F32 && concept->data.F32 - (int)concept->data.F32 == 0) {
+            offset = concept->data.F32;
+        } else if (concept->type == PS_TYPE_F64 && concept->data.F64 - (int)concept->data.F64 == 0) {
+            offset = concept->data.F64;
+        } else {
+            psError(PS_ERR_UNKNOWN, true, "Concept %s is not of integer type, as expected.\n", pattern->name);
+            return NULL;
+        }
+    }
+    offset -= fortranCorr(cameraFormat, pattern->name);
+    return psMetadataItemAllocS32(pattern->name, pattern->comment, offset);
+}
+
+
+psMetadataItem *p_pmConceptFormat_CELL_TRIMSEC(psMetadataItem *concept,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell)
+{
+    assert(concept);
+
+    psRegion *trimsec = concept->data.V; // The trimsec region
+    psString trimsecString = psRegionToString(*trimsec);
+    psMetadataItem *formatted = psMetadataItemAllocStr(concept->name, concept->comment,
+                                trimsecString);
+    psFree(trimsecString);
+    return formatted;
+}
+
+psMetadataItem *p_pmConceptFormat_CELL_BIASSEC(psMetadataItem *concept,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell)
+{
+    // Return a metadata item containing a list of metadata items of region strings
+    psList *biassecs = concept->data.V; // The biassecs region list
+    psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+    psRegion *region = NULL;            // Region from iteration
+    psList *new = psListAlloc(NULL);    // New list containing metadatas
+    while ((region = psListGetAndIncrement(biassecsIter))) {
+        psString regionString = psRegionToString(*region); // The string region "[x0:x1,y0:y1]"
+        psMetadataItem *item = psMetadataItemAllocStr(concept->name, concept->comment, regionString);
+        psFree(regionString);           // Drop reference
+        psListAdd(new, PS_LIST_TAIL, item);
+        psFree(item);                   // Drop reference
+    }
+    psFree(biassecsIter);
+    psMetadataItem *formatted = psMetadataItemAllocPtr(concept->name, PS_DATA_LIST, concept->comment, new);
+    psFree(new);                        // Drop reference
+    return formatted;
+}
+
+// This function actually does both CELL.XBIN and CELL.YBIN if CELL.XBIN and CELL.YBIN are specified by the
+// same header.
+psMetadataItem *p_pmConceptFormat_CELL_XBIN(psMetadataItem *concept,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell)
+{
+    assert(concept);
+
+    psMetadata *translation = psMetadataLookupMD(NULL, cameraFormat, "TRANSLATION");
+    bool xBinOK = true, yBinOK = true;  // Status of MD lookups
+    psString xKeyword = psMetadataLookupStr(&xBinOK, translation, "CELL.XBIN");
+    psString yKeyword = psMetadataLookupStr(&yBinOK, translation, "CELL.YBIN");
+    if (xBinOK && yBinOK && strlen(xKeyword) > 0 && strlen(yKeyword) > 0 &&
+            strcasecmp(xKeyword, yKeyword) == 0) {
+        psMetadataItem *yBinItem = psMetadataLookup(cell->concepts, "CELL.YBIN"); // Binning factor in y
+        psString binString = psStringCopy("");
+        psStringAppend(&binString, "%d %d", concept->data.S32, yBinItem->data.S32);
+        psMetadataItem *binItem = psMetadataItemAllocStr(concept->name, concept->comment, binString);
+        psFree(binString);
+        return binItem;
+    }
+
+    // Otherwise, there's no formatting required
+    return psMemIncrRefCounter(concept);
+}
+
+// Only need to format if both if CELL.XBIN and CELL.YBIN are not specified by the same header.
+psMetadataItem *p_pmConceptFormat_CELL_YBIN(psMetadataItem *concept,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell)
+{
+    assert(concept);
+
+    psMetadata *translation = psMetadataLookupMD(NULL, cameraFormat, "TRANSLATION");
+    bool xBinOK = true, yBinOK = true;  // Status of MD lookups
+    psString xKeyword = psMetadataLookupStr(&xBinOK, translation, "CELL.XBIN");
+    psString yKeyword = psMetadataLookupStr(&yBinOK, translation, "CELL.YBIN");
+    if (xBinOK && yBinOK && strlen(xKeyword) > 0 && strlen(yKeyword) > 0 &&
+            strcasecmp(xKeyword, yKeyword) == 0) {
+        // Censor this --- it's already done (though no harm if it's done twice
+        return NULL;
+    }
+
+    // No formatting required
+    return psMemIncrRefCounter(concept);
+}
+
+
+psMetadataItem *p_pmConceptFormat_TIMESYS(psMetadataItem *concept,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell)
+{
+    psString sys = NULL;            // String to store
+    switch (concept->data.S32) {
+    case PS_TIME_TAI:
+        sys = psStringCopy("TAI");
+        break;
+    case PS_TIME_UTC:
+        sys = psStringCopy("UTC");
+        break;
+    case PS_TIME_UT1:
+        sys = psStringCopy("UT1");
+        break;
+    case PS_TIME_TT:
+        sys = psStringCopy("TT");
+        break;
+    default:
+        sys = psStringCopy("Unknown");
+    }
+    psMetadataItem *newItem = psMetadataItemAllocStr(concept->name, concept->comment, sys);
+    psFree(sys);
+
+    return newItem;
+}
+
+psMetadataItem *p_pmConceptFormat_TIME(psMetadataItem *concept,
+                                       psMetadata *cameraFormat,
+                                       pmFPA *fpa,
+                                       pmChip *chip,
+                                       pmCell *cell)
+{
+    psTime *time = concept->data.V;     // The time
+
+    // Work out the format
+    bool separateTime = false;          // Are the date and time stored separately?
+    bool pre2000Time = false;           // Is the year in pre-2000 format (two digits only)?
+    bool backwardsTime = false;         // Is the date stored backwards (DD-MM-YYYY)?
+    bool usaTime = false;               // Is the date stored in USA order (MM-DD-YYYY)?
+    bool jdTime = false;                // Is the date stored as a JD?
+    bool mjdTime = false;               // Is the date stored as a MJD?
+
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMD(&mdok, cameraFormat, "FORMATS"); // The formats
+    if (mdok && formats) {
+        psString format = psMetadataLookupStr(&mdok, formats, concept->name); // The formats for eg, CELL.TIME
+        if (mdok && format && strlen(format) > 0) {
+            psList *formatList = psStringSplit(format, " ,;", false); // List of formats specified
+            psListIterator *formatListIter = psListIteratorAlloc(formatList, PS_LIST_HEAD, false); // Iterator
+            while ((format = psListGetAndIncrement(formatListIter))) {
+                if (strcasecmp(format, "SEPARATE") == 0) {
+                    separateTime = true;
+                } else if (strcasecmp(format, "PRE2000") == 0) {
+                    pre2000Time = true;
+                } else if (strcasecmp(format, "BACKWARDS") == 0) {
+                    backwardsTime = true;
+                    usaTime = false;
+                    mjdTime = false;
+                    jdTime = false;
+                } else if (strcasecmp(format, "USA") == 0) {
+                    usaTime = true;
+                    backwardsTime = false;
+                    jdTime = false;
+                    mjdTime = false;
+                } else if (strcasecmp(format, "JD") == 0) {
+                    jdTime = true;
+                    usaTime = false;
+                    backwardsTime = false;
+                    mjdTime = false;
+                    separateTime = false;
+                } else if (strcasecmp(format, "MJD") == 0) {
+                    mjdTime = true;
+                    usaTime = false;
+                    backwardsTime = false;
+                    jdTime = false;
+                    separateTime = false;
+                } else {
+                    psLogMsg(__func__, PS_LOG_WARN, "Unrecognised FORMATS option for %s: %s --- "
+                             "ignored.\n", concept->name, format);
+                }
+            }
+            psFree(formatListIter);
+            psFree(formatList);
+        }
+    }
+
+    if (separateTime) {
+        // We're working with two separate headers --- construct a list with the date and time separately
+        psString dateTimeString = psTimeToISO(time); // String representation
+        psList *dateTime = psStringSplit(dateTimeString, "T", true);
+        psFree(dateTimeString);
+        psString dateString = psListGet(dateTime, PS_LIST_HEAD); // The date string
+        psString timeString = psListGet(dateTime, PS_LIST_TAIL); // The time string
+
+        // Need to format the strings....
+        // XXX: Couldn't be bothered doing these right now
+        if (pre2000Time) {
+            psError(PS_ERR_UNKNOWN, true, "Don't you realise it's the twenty-first century?\n");
+            return NULL;
+        }
+        if (backwardsTime) {
+            psError(PS_ERR_UNKNOWN, true, "You want it BACKWARDS?  Not right now, thanks.\n");
+            return NULL;
+        }
+        if (usaTime) {
+            psError(PS_ERR_UNKNOWN, true, "USA?  No OK.\n");
+            return NULL;
+        }
+
+        psMetadataItem *dateItem = psMetadataItemAllocStr(concept->name, "The date of observation",
+                                   dateString);
+        psMetadataItem *timeItem = psMetadataItemAllocStr(concept->name, "The time of observation",
+                                   timeString);
+        psFree(dateString);
+        psFree(timeString);
+
+        psListRemove(dateTime, PS_LIST_HEAD);
+        psListRemove(dateTime, PS_LIST_HEAD);
+        psListAdd(dateTime, PS_LIST_HEAD, dateItem);
+        psListAdd(dateTime, PS_LIST_TAIL, timeItem);
+
+        psMetadataItem *item = psMetadataItemAllocPtr(concept->name, PS_DATA_LIST, concept->comment,
+                               dateTime);
+        return item;
+    }
+    if (jdTime) {
+        double jd = psTimeToMJD(time);
+        return psMetadataItemAllocF64(concept->name, concept->comment, jd);
+    }
+    if (mjdTime) {
+        double mjd = psTimeToMJD(time);
+        return psMetadataItemAllocF64(concept->name, concept->comment, mjd);
+    }
+
+    // If we've gotten this far, so it's straight ISO.
+    psString dateTimeString = psTimeToISO(time); // String representation
+    psMetadataItem *item = psMetadataItemAllocStr(concept->name, concept->comment, dateTimeString);
+    psFree(dateTimeString);
+    return item;
+}
+
+psMetadataItem *p_pmConceptFormat_Positions(psMetadataItem *concept,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell)
+{
+    assert(concept);
+    assert(cameraFormat);
+
+    if (concept->type != PS_TYPE_S32) {
+        psError(PS_ERR_UNKNOWN, true, "Concept %s is not of type S32, as expected.\n", concept->name);
+        return NULL;
+    }
+    int offset = concept->data.S32;
+    offset += fortranCorr(cameraFormat, concept->name);
+    return psMetadataItemAllocS32(concept->name, concept->comment, offset);
+}
+
Index: /tags/RHL_v1_1/psModules/src/concepts/pmConceptsStandard.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/concepts/pmConceptsStandard.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/concepts/pmConceptsStandard.h	(revision 9533)
@@ -0,0 +1,92 @@
+#ifndef PM_CONCEPTS_STANDARD_H
+#define PM_CONCEPTS_STANDARD_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+// Functions to parse and format the standard concepts
+
+psMetadataItem *p_pmConceptParse_FPA_Coords(psMetadataItem *concept,
+        psMetadataItem *pattern,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell);
+psMetadataItem *p_pmConceptFormat_FPA_Coords(psMetadataItem *concept,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell);
+psMetadataItem *p_pmConceptParse_CELL_TRIMSEC(psMetadataItem *concept,
+        psMetadataItem *pattern,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell);
+psMetadataItem *p_pmConceptParse_CELL_BIASSEC(psMetadataItem *concept,
+        psMetadataItem *pattern,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell);
+psMetadataItem *p_pmConceptParse_CELL_Binning(psMetadataItem *concept,
+        psMetadataItem *pattern,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell);
+psMetadataItem *p_pmConceptParse_TIMESYS(psMetadataItem *concept,
+        psMetadataItem *pattern,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell);
+psMetadataItem *p_pmConceptParse_TIME(psMetadataItem *concept,
+                                      psMetadataItem *pattern,
+                                      psMetadata *cameraFormat,
+                                      pmFPA *fpa,
+                                      pmChip *chip,
+                                      pmCell *cell);
+psMetadataItem *p_pmConceptParse_Positions(psMetadataItem *concept,
+        psMetadataItem *pattern,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell);
+psMetadataItem *p_pmConceptFormat_CELL_TRIMSEC(psMetadataItem *concept,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell);
+psMetadataItem *p_pmConceptFormat_CELL_BIASSEC(psMetadataItem *concept,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell);
+psMetadataItem *p_pmConceptFormat_CELL_XBIN(psMetadataItem *concept,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell);
+psMetadataItem *p_pmConceptFormat_CELL_YBIN(psMetadataItem *concept,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell);
+psMetadataItem *p_pmConceptFormat_TIMESYS(psMetadataItem *concept,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell);
+psMetadataItem *p_pmConceptFormat_TIME(psMetadataItem *concept,
+                                       psMetadata *cameraFormat,
+                                       pmFPA *fpa,
+                                       pmChip *chip,
+                                       pmCell *cell);
+psMetadataItem *p_pmConceptFormat_Positions(psMetadataItem *concept,
+        psMetadata *cameraFormat,
+        pmFPA *fpa,
+        pmChip *chip,
+        pmCell *cell);
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/concepts/pmConceptsWrite.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/concepts/pmConceptsWrite.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/concepts/pmConceptsWrite.c	(revision 9533)
@@ -0,0 +1,541 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <pslib.h>
+
+#include "pmFPA.h"
+#include "pmHDU.h"
+#include "pmHDUUtils.h"
+#include "pmConcepts.h"
+#include "pmConceptsRead.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static bool compareConcepts(psMetadataItem *compare, // Item to compare
+                            psMetadataItem *standard // Standard for comparison
+                           )
+{
+    // First order checks
+    if (! compare || ! standard) {
+        return false;
+    }
+    if (strcasecmp(compare->name, standard->name) != 0) {
+        return false;
+    }
+
+    // Special case: list
+    if (compare->type == PS_DATA_LIST) {
+        // "compare" contains a list of psMetadataItems
+        // "standard" likely contains just a string (but it might possibly be a list of strings)
+        psList *cList = compare->data.V; // The list from comparison item
+        psList *sList = NULL;         // The list from standard item
+        switch (standard->type) {
+        case PS_DATA_STRING:
+            sList = psStringSplit(standard->data.V, " ;", true);
+            break;
+        case PS_DATA_LIST:
+            sList = psMemIncrRefCounter(standard->data.V);
+            break;
+        default:
+            return false;
+        }
+        if (cList->n != sList->n) {
+            psFree(sList);
+            return false;
+        }
+        psVector *match = psVectorAlloc(cList->n, PS_TYPE_U8); // Array indicating which values match
+        match->n = cList->n;
+        psVectorInit(match, 0);
+        psListIterator *cIter = psListIteratorAlloc(cList, PS_LIST_HEAD, false); // compare iterator
+        psListIterator *sIter = psListIteratorAlloc(sList, PS_LIST_HEAD, false); // standard iterator
+        psMetadataItem *cItem = NULL; // Item from compare list
+        while ((cItem = psListGetAndIncrement(cIter))) {
+            if (cItem->type != PS_DATA_STRING) {
+                psLogMsg(__func__, PS_LOG_WARN, "psMetadataItem from list is of type %x instead of "
+                         "%x (PS_DATA_STRING) --- can't interpret.\n", cItem->type, PS_DATA_STRING);
+                psFree(cIter);
+                psFree(sIter);
+                psFree(match);
+                psFree(sList);
+                return false;
+            }
+            psString cString = cItem->data.V; // String from compare list
+            psListIteratorSet(sIter, PS_LIST_HEAD);
+            int index = 0;            // Index for list
+            bool found = false;       // Found a match?
+            for (psString sString = NULL; (sString = psListGetAndIncrement(sIter)) && !found; index++) {
+                if (strcasecmp(cString, sString) == 0) {
+                    match->data.U8[index]++;
+                    found = true;
+                }
+            }
+            if (! found) {
+                // Can give up immediately
+                psFree(cIter);
+                psFree(sIter);
+                psFree(match);
+                psFree(sList);
+                return false;
+            }
+        }
+        // Make sure we got 100% matches in both directions
+        bool allMatch = true;         // Did all of them match?
+        for (int i = 0; i < match->n && allMatch; i++) {
+            if (!match->data.U8[i]) {
+                allMatch = false;
+            }
+        }
+        psFree(cIter);
+        psFree(sIter);
+        psFree(sList);
+        psFree(match);
+        return allMatch;
+    }
+
+    return psMetadataItemCompare(compare, standard);
+
+}
+
+
+// Format a single concept
+static psMetadataItem *conceptFormat(pmConceptSpec *spec, // The concept specification
+                                     psMetadataItem *concept, // The concept to parse
+                                     psMetadata *cameraFormat, // The camera format
+                                     pmFPA *fpa, // The FPA
+                                     pmChip *chip, // The chip
+                                     pmCell *cell // The cell
+                                    )
+{
+    assert(spec);
+    assert(cameraFormat);
+
+    if (concept) {
+        psMetadataItem *formatted = NULL;  // The formatted concept
+        if (spec->format) {
+            formatted = spec->format(concept, cameraFormat, fpa, chip, cell);
+        } else {
+            formatted = psMemIncrRefCounter(concept);
+        }
+
+        return formatted;
+    }
+    return NULL;
+}
+
+// Write a single value to a header
+static bool writeSingleHeader(pmHDU *hdu, // HDU for which to add to the header
+                              const char *keyword, // Keyword to add
+                              psMetadataItem *item // Item to add to the header; may be NULL
+                             )
+{
+    assert(hdu);
+    assert(keyword && strlen(keyword) > 0);
+
+    if (!hdu->header) {
+        hdu->header = psMetadataAlloc();
+    }
+    if (!item) {
+        psTrace("psModules.concepts", 9, "Writing header %s: <<<BLANK>>>\n", keyword);
+        // Assume it's a NULL string: it's most easily parsed.
+        return psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, NULL, NULL);
+    }
+    switch (item->type) {
+    case PS_DATA_STRING:
+        psTrace("psModules.concepts", 9, "Writing header %s: %s\n", keyword, item->data.str);
+        return psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                item->data.V);
+    case PS_DATA_S32:
+        psTrace("psModules.concepts", 9, "Writing header %s: %d\n", keyword, item->data.S32);
+        return psMetadataAddS32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                item->data.S32);
+    case PS_DATA_F32:
+        psTrace("psModules.concepts", 9, "Writing header %s: %f\n", keyword, item->data.F32);
+        return psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                item->data.F32);
+    case PS_DATA_F64:
+        psTrace("psModules.concepts", 9, "Writing header %s: %f\n", keyword, item->data.F64);
+        return psMetadataAddF64(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                item->data.F64);
+    case PS_DATA_REGION: {
+            psString region = psRegionToString(*(psRegion*)item->data.V);
+            psTrace("psModules.concepts", 9, "Writing header %s: %s\n", keyword, region);
+            bool result = psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                           region);
+            psFree(region);
+            return result;
+        }
+    default:
+        psLogMsg(__func__, PS_LOG_WARN, "Type of %s is not suitable for a FITS header --- not added.\n",
+                 item->name);
+        return false;
+    }
+}
+
+
+// Write potentially multiple values to a header
+static bool writeHeader(pmHDU *hdu,     // HDU for which to add to the header
+                        const char *keywords, // Keywords to add
+                        psMetadataItem *item // Item to add to the header
+                       )
+{
+    assert(hdu);
+    assert(keywords);
+    assert(item);
+
+    bool status = true;                 // Status of writing headers, to be returned
+    if (item->type == PS_DATA_LIST) {
+        psList *values = item->data.V;  // List of outputs
+        psList *keys = psStringSplit(keywords, " ,;", true); // List of keywords
+        if (keys->n != values->n && values->n != 0) {
+            psError(PS_ERR_UNKNOWN, true, "Number of keywords (%ld) does not match number of "
+                    "values (%ld).\n", keys->n, values->n);
+            psFree(keys);
+            return false;
+        }
+        psListIterator *keysIter = psListIteratorAlloc(keys, PS_LIST_HEAD, false); // Iterator for keywords
+        psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false); // Iterator for values
+        psString key = NULL;            // Keyword from iteration
+        while ((key = psListGetAndIncrement(keysIter))) {
+            psMetadataItem *value = psListGetAndIncrement(valuesIter); // Value from iteration; may be NULL
+            status |= writeSingleHeader(hdu, key, value);
+        }
+        psFree(keysIter);
+        psFree(valuesIter);
+        psFree(keys);
+    } else {
+        status = writeSingleHeader(hdu, keywords, item);
+    }
+    return status;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmConceptsWriteToCamera(psMetadata *specs, // The concept specifications
+                             pmCell *cell,   // The cell
+                             psMetadata *concepts // The concepts
+                            )
+{
+    PS_ASSERT_PTR_NON_NULL(specs, false);
+    PS_ASSERT_PTR_NON_NULL(concepts, false);
+    if (!cell) {
+        return false;
+    }
+    if (!cell->config)
+        return false;
+
+    pmHDU *hdu = pmHDUGetLowest(NULL, NULL, cell); // The HDU at the lowest level
+    if (!hdu) {
+        return false;
+    }
+    psMetadata *cameraFormat = hdu->format; // The camera format
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psString name = specItem->name; // The concept name
+        psMetadataItem *cameraItem = psMetadataLookup(cell->config, name); // The concept from the camera,
+        // or NULL
+        if (cameraItem) {
+            // Grab the concept
+            psMetadataItem *conceptItem = psMetadataLookup(concepts, name); // The concept
+            // Formatted version
+            psMetadataItem *formatted = conceptFormat(spec, conceptItem, cameraFormat, NULL, NULL, cell);
+            if (!formatted) {
+                continue;
+            }
+            psString nameSource = NULL; // String with the concept name and ".SOURCE" added
+            psStringAppend(&nameSource, "%s.SOURCE", name);
+            bool mdok = true;       // Status of MD lookup
+            psString source = psMetadataLookupStr(&mdok, cell->config, nameSource); // The source
+            if (mdok && strlen(source) > 0) {
+                psTrace("psModules.concepts", 8, "%s is %s\n", nameSource, source);
+                if (strcasecmp(source, "HEADER") == 0) {
+                    if (cameraItem->type != PS_DATA_STRING) {
+                        psLogMsg(__func__, PS_LOG_WARN, "Concept %s is specified by header, but is not "
+                                 "of type STR --- ignored.\n", conceptItem->name);
+                        continue;
+                    }
+                    psTrace("psModules.concepts", 8, "Writing %s to header %s\n", name, cameraItem->data.str);
+                    writeHeader(hdu, cameraItem->data.V, formatted);
+                } else if (strcasecmp(source, "VALUE") == 0) {
+                    psTrace("psModules.concepts", 8, "Checking %s against camera format.\n", name);
+                    if (! compareConcepts(formatted, cameraItem)) {
+                        psLogMsg(__func__, PS_LOG_WARN, "Concept %s is specified by value in the camera "
+                                 "format, but the values don't match.\n", name);
+                    }
+                } else {
+                    psLogMsg(__func__, PS_LOG_WARN, "Concept source %s isn't HEADER or VALUE --- can't "
+                             "write\n", nameSource);
+                }
+            } else if (! compareConcepts(formatted, cameraItem)) {
+                // Assume it's specified by value
+                psLogMsg(__func__, PS_LOG_WARN, "Concept %s is specified by value in the camera "
+                         "format, but the values don't match.\n", name);
+            }
+            psFree(formatted);
+            psFree(nameSource);
+        }
+
+    }
+    psFree(specsIter);
+    return true;
+}
+
+bool pmConceptsWriteToDefaults(psMetadata *specs, // The concept specifications
+                               pmFPA *fpa, // The FPA
+                               pmChip *chip, // The chip
+                               pmCell *cell, // The cell
+                               psMetadata *concepts // The concepts
+                              )
+{
+    PS_ASSERT_PTR_NON_NULL(specs, false);
+    PS_ASSERT_PTR_NON_NULL(concepts, false);
+
+    pmHDU *hdu = pmHDUGetLowest(NULL, NULL, cell); // The HDU at the lowest level
+    if (!hdu) {
+        return false;
+    }
+    psMetadata *cameraFormat = hdu->format; // The camera format
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *defaults = psMetadataLookupMD(&mdok, cameraFormat, "DEFAULTS"); // The DEFAULTS spec
+    if (!mdok || !defaults) {
+        return false;
+    }
+
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psString name = specItem->name; // The concept name
+        psMetadataItem *defaultItem = psMetadataLookup(defaults, name); // The item from the DEFAULTS
+        if (defaultItem) {
+            psMetadataItem *conceptItem = NULL; // The item from the concepts
+            if (defaultItem->type == PS_DATA_METADATA) {
+                // It's a menu --- need to look up the .DEPEND
+                psString dependName = NULL; // The concept name with ".DEPEND" on the end
+                psStringAppend(&dependName, ".DEPEND");
+                psString dependKey = psMetadataLookupStr(&mdok, defaults, dependName); // The keyword
+                psFree(dependName);
+                if (!mdok || !dependKey || strlen(dependKey) == 0) {
+                    psLogMsg(__func__, PS_LOG_WARN, "Can't find %s in the DEFAULTS for %s --- ignored.\n",
+                             dependName, name);
+                    continue;
+                }
+                psString dependValue = psMetadataLookupStr(&mdok, concepts, dependName); // The value
+                if (!mdok || !dependKey || strlen(dependKey) == 0) {
+                    psLogMsg(__func__, PS_LOG_WARN, "Concept %s specified by %s isn't of type STR -- "
+                             "ignored.\n", name, dependName);
+                    continue;
+                }
+                conceptItem = psMetadataLookup(defaultItem->data.V, dependValue);
+            } else {
+                conceptItem = psMetadataLookup(concepts, name); // The item from the concepts
+            }
+            psMetadataItem *formatted = conceptFormat(spec, conceptItem, cameraFormat, fpa, chip, cell);
+            if (!formatted) {
+                continue;
+            }
+            if (! compareConcepts(formatted, defaultItem)) {
+                psLogMsg(__func__, PS_LOG_WARN, "Concept %s is specified by the DEFAULTS in the camera "
+                         "format, but the values don't match.\n", name);
+            }
+            psFree(formatted);
+        }
+    }
+    psFree(specsIter);
+    return true;
+}
+
+
+bool pmConceptsWriteToHeader(psMetadata *specs, // The concept specifications
+                             pmFPA *fpa, // The FPA
+                             pmChip *chip, // The chip
+                             pmCell *cell, // The cell
+                             psMetadata *concepts // The concepts
+                            )
+{
+    PS_ASSERT_PTR_NON_NULL(specs, false);
+    PS_ASSERT_PTR_NON_NULL(concepts, false);
+
+    pmHDU *hdu = pmHDUGetLowest(NULL, NULL, cell); // The HDU at the lowest level
+    if (!hdu) {
+        return false;
+    }
+    psMetadata *cameraFormat = hdu->format; // The camera format
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *translation = psMetadataLookupMD(&mdok, cameraFormat, "TRANSLATION"); // The TRANSLATION spec
+    if (!mdok || !translation) {
+        return false;
+    }
+
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psString name = specItem->name; // The concept name
+        psMetadataItem *headerItem = psMetadataLookup(translation, name); // The item from the TRANSLATION
+        if (headerItem) {
+            if (headerItem->type != PS_DATA_STRING) {
+                psLogMsg(__func__, PS_LOG_WARN, "TRANSLATION keyword for concept %s isn't of type STR ---"
+                         " ignored.", name);
+                continue;
+            }
+            psTrace("psModules.concepts", 3, "Writing %s to header %s\n", name, headerItem->data.str);
+            psMetadataItem *conceptItem = psMetadataLookup(concepts, name); // The item from the concepts
+            psMetadataItem *formatted = conceptFormat(spec, conceptItem, cameraFormat, fpa, chip, cell);
+            if (!formatted) {
+                continue;
+            }
+            writeHeader(hdu, headerItem->data.V, formatted);
+            psFree(formatted);
+        }
+    }
+    psFree(specsIter);
+    return true;
+}
+
+// XXX Warning: This code has not been tested at all
+bool pmConceptsWriteToDatabase(psMetadata *specs, // The concept specifications
+                               pmFPA *fpa, // The FPA
+                               pmChip *chip, // The chip
+                               pmCell *cell, // The cell
+                               psDB *db,// The database handle
+                               psMetadata *concepts // The concepts
+                              )
+{
+    PS_ASSERT_PTR_NON_NULL(specs, false);
+    PS_ASSERT_PTR_NON_NULL(concepts, false);
+
+    if (!db) {
+        return false;
+    }
+
+    #ifdef OMIT_PSDB
+    return false;
+    #else
+
+    pmHDU *hdu = pmHDUGetLowest(NULL, NULL, cell); // The HDU at the lowest level
+    if (!hdu) {
+        return false;
+    }
+    psMetadata *cameraFormat = hdu->format; // The camera format
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *database = psMetadataLookupMD(&mdok, cameraFormat, "DATABASE"); // The DATABASE spec
+    if (!mdok || !database) {
+        return false;
+    }
+    psMetadataIterator *specsIter = psMetadataIteratorAlloc(specs, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *specItem = NULL;    // Item from the specs metadata
+    while ((specItem = psMetadataGetAndIncrement(specsIter))) {
+        pmConceptSpec *spec = specItem->data.V; // The specification
+        psString name = specItem->name; // The concept name
+
+        psMetadataItem *dbItem = psMetadataLookup(database, name); // The item from the DATABASE
+        if (dbItem) {
+            if (dbItem->type != PS_DATA_METADATA) {
+                psLogMsg(__func__, PS_LOG_WARN, "DATABASE keyword for concept %s isn't of type METADATA "
+                         "--- ignored.\n", name);
+                continue;
+            }
+
+            psMetadataItem *conceptItem = psMetadataLookup(concepts, name); // The item from the concepts
+            psMetadataItem *formatted = conceptFormat(spec, conceptItem, cameraFormat, fpa, chip, cell);
+            if (!formatted) {
+                continue;
+            }
+
+            psMetadata *dbLookup = dbItem->data.V; // How to look up the value of interest
+            // Name of the table
+            const char *tableName = psMetadataLookupStr(&mdok, dbLookup, "TABLE");
+            // Name of "where" columns
+            const char *givenCols = psMetadataLookupStr(&mdok, dbLookup, "GIVENDBCOL");
+            // Values for "where" columns
+            const char *givenPS = psMetadataLookupStr(&mdok, dbLookup, "GIVENPS");
+
+            // Now, need to get the "given"s
+            if (strlen(givenCols) || strlen(givenPS)) {
+                psList *cols = psStringSplit(givenCols, ",;", true); // List of column names
+                psList *values = psStringSplit(givenPS, ",;", true); // List of value names for the columns
+                psMetadata *selection = psMetadataAlloc(); // The stuff to select in the DB
+                if (cols->n != values->n) {
+                    psLogMsg(__func__, PS_LOG_WARN,
+                             "The GIVENDBCOL and GIVENPS entries for %s do not have "
+                             "the same number of entries --- ignored.\n", name);
+                } else {
+                    // Iterators for the lists
+                    psListIterator *colsIter = psListIteratorAlloc(cols, PS_LIST_HEAD, false);
+                    psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false);
+                    char *column = NULL;    // Name of the column
+                    while ((column = psListGetAndIncrement(colsIter))) {
+                        char *dependName = psListGetAndIncrement(valuesIter); // Name for the value
+                        if (!strlen(column) || !strlen(name)) {
+                            psLogMsg(__func__, PS_LOG_WARN, "One of the columns or value names for %s is "
+                                     " empty --- ignored.\n", name);
+                        } else {
+                            // Search for the value name
+                            psMetadataItem *item = NULL; // The value
+                            if (!item && cell) {
+                                item = psMetadataLookup(cell->concepts, dependName);
+                            }
+                            if (!item && chip) {
+                                item = psMetadataLookup(chip->concepts, dependName);
+                            }
+                            if (!item && fpa) {
+                                item = psMetadataLookup(fpa->concepts, dependName);
+                            }
+                            if (! item) {
+                                psLogMsg(__func__, PS_LOG_ERROR,
+                                         "Unable to find the value name %s for DB "
+                                         " lookup on %s --- ignored.\n", dependName, name);
+                            } else {
+                                // We need to create a new psMetadataItem.  I don't think we can't simply
+                                // hack the existing one, since that could conceivably cause memory leaks
+                                psMetadataAddItem(selection, formatted, PS_LIST_TAIL, PS_META_REPLACE);
+                                psFree(formatted);
+                            }
+                        }
+                        psFree(dependName);
+                        psFree(column);
+                    } // Iterating through the columns
+                    psFree(colsIter);
+                    psFree(valuesIter);
+
+                    // Check first to make sure we're only going to touch one row
+                    psArray *dbResult = psDBSelectRows(db, tableName, selection, 2); // Lookup result
+                    // Note that we use limit=2 in order to test if there are multiple rows returned
+                    if (! dbResult || dbResult->n == 0) {
+                        psLogMsg(__func__, PS_LOG_WARN, "Unable to find any rows in DB for %s --- "
+                                 "ignored\n", name);
+                        return false;
+                    } else {
+                        if (dbResult->n > 1) {
+                            psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s "
+                                     "--- ignored.\n", name);
+                        }
+                        // Update the DB
+                        psMetadata *update = psMetadataAlloc();
+                        psMetadataAddItem(update, conceptItem, PS_LIST_HEAD, 0);
+                        psDBUpdateRows(db, tableName, selection, update);
+                        psFree(update);
+                        return true;
+                    }
+                }
+                psFree(cols);
+                psFree(values);
+            } // Doing the "given"s.
+        }
+    }
+    psFree(specsIter);
+    return true;
+    #endif
+}
+
Index: /tags/RHL_v1_1/psModules/src/concepts/pmConceptsWrite.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/concepts/pmConceptsWrite.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/concepts/pmConceptsWrite.h	(revision 9533)
@@ -0,0 +1,78 @@
+#ifndef PM_CONCEPTS_WRITE_H
+#define PM_CONCEPTS_WRITE_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+bool pmConceptsWriteToCamera(psMetadata *specs, // The concept specifications
+                             pmCell *cell,   // The cell
+                             psMetadata *concepts // The concepts
+                            );
+bool pmConceptsWriteToDefaults(psMetadata *specs, // The concept specifications
+                               pmFPA *fpa, // The FPA
+                               pmChip *chip, // The chip
+                               pmCell *cell, // The cell
+                               psMetadata *concepts // The concepts
+                              );
+bool pmConceptsWriteToHeader(psMetadata *specs, // The concept specifications
+                             pmFPA *fpa, // The FPA
+                             pmChip *chip, // The chip
+                             pmCell *cell, // The cell
+                             psMetadata *concepts // The concepts
+                            );
+bool pmConceptsWriteToDatabase(psMetadata *specs, // The concept specifications
+                               pmFPA *fpa, // The FPA
+                               pmChip *chip, // The chip
+                               pmCell *cell, // The cell
+                               psDB *db,// The database handle
+                               psMetadata *concepts // The concepts
+                              );
+
+
+#ifdef OLD
+
+// Well, not really "write", but check to make sure it's there and matches
+bool pmConceptWriteToCamera(pmCell *cell, // The cell
+                            psMetadataItem *concept // Concept
+                           );
+
+// Write the concept to the header in the appropriate location
+bool pmConceptWriteToHeader(pmFPA *fpa, // The FPA that contains the chip
+                            pmChip *chip, // The chip that contains the cell
+                            pmCell *cell, // The cell
+                            psMetadataItem *concept // Concept
+                           );
+
+// Well, not really "write", but check to see if it's there, and matches
+bool pmConceptWriteToDefault(pmFPA *fpa, // The FPA that contains the chip
+                             pmChip *chip, // The chip that contains the cell
+                             pmCell *cell, // The cell
+                             psMetadataItem *concept // Concept
+                            );
+
+bool pmConceptWriteToDB(pmFPA *fpa, // The FPA that contains the chip
+                        pmChip *chip, // The chip that contains the cell
+                        pmCell *cell, // The cell
+                        psDB *db,    // DB handle
+                        psMetadataItem *concept // Concept
+                       );
+
+// Concept write from item
+bool pmConceptWriteItem(pmFPA *fpa,     // The FPA
+                        pmChip *chip,   // The chip
+                        pmCell *cell,   // The cell
+                        psDB *db,       // DB handle
+                        psMetadataItem *concept // Concept item
+                       );
+
+bool pmConceptWrite(pmFPA *fpa, // The FPA
+                    pmChip *chip,// The chip
+                    pmCell *cell,    // The cell
+                    psDB *db, // DB handle
+                    psMetadata *concepts, // Concepts MD from which to set
+                    const char *name // Name of the concept
+                   );
+
+#endif
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/config/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/src/config/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/config/.cvsignore	(revision 9533)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/RHL_v1_1/psModules/src/config/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/src/config/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/config/Makefile.am	(revision 9533)
@@ -0,0 +1,11 @@
+noinst_LTLIBRARIES = libpsmodulesconfig.la
+
+libpsmodulesconfig_la_CPPFLAGS = $(SRCINC) $(PSMODULES_CFLAGS)
+libpsmodulesconfig_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulesconfig_la_SOURCES  = \
+    pmConfig.c
+
+pkginclude_HEADERS = \
+    pmConfig.h
+
+CLEANFILES = *~
Index: /tags/RHL_v1_1/psModules/src/config/pmConfig.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/config/pmConfig.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/config/pmConfig.c	(revision 9533)
@@ -0,0 +1,1015 @@
+/** @file  pmConfig.h
+ *
+ *  @author PAP, IfA
+ *
+ *  @version $Revision: 1.46 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-10-10 01:03:13 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <glob.h>
+#include <pslib.h>
+#include "pmConfig.h"
+
+#define PS_SITE "PS_SITE"         // Name of the environment variable containing the site config file
+#define PS_DEFAULT_SITE ".ipprc"  // Default site config file
+
+static psArray *configPath = NULL;
+
+static void configFree(pmConfig *config)
+{
+    psFree(config->site);
+    psFree(config->files);
+    psFree(config->camera);
+    psFree(config->cameraName);
+    psFree(config->recipes);
+    psFree(config->recipesSource);
+    psFree(config->arguments);
+    psFree(config->database);
+}
+
+pmConfig *pmConfigAlloc(int *argc, char **argv)
+{
+    pmConfig *config = psAlloc(sizeof(pmConfig));
+    (void)psMemSetDeallocator(config, (psFreeFunc)configFree);
+
+    // Initialise
+    config->site = NULL;
+    config->camera = NULL;
+    config->cameraName = NULL;
+    config->recipes = psMetadataAlloc();
+    config->recipesRead = P_PM_RECIPE_SOURCE_NONE;
+    config->recipesSource = psMetadataAlloc();
+    config->arguments = psMetadataAlloc();
+    config->database = NULL;
+    config->argc = argc;
+    config->argv = argv;
+
+    // the file structure is used to carry pmFPAfiles
+    config->files = psMetadataAlloc ();
+    return config;
+}
+
+void pmConfigSet(const char *path)
+{
+    pmConfigDone();
+
+    psList *list = psStringSplit(path, ":", false);
+    configPath = psListToArray(list);
+    psFree(list);
+}
+
+void pmConfigDone(void)
+{
+    psFree(configPath);
+    configPath = NULL;
+}
+
+
+/** readConfig
+ *
+ * This function attempts to open the file specified in the parameter list
+ * parse it into metadata, then return it as a psMetaData table.
+ *
+ */
+bool readConfig(
+    psMetadata **config,                // Config to output
+    const char *name,                   // Name of file
+    const char *description)            // Description of file
+{
+    assert(config);
+    assert(name && strlen(name) > 0);
+    assert(description && strlen(description) > 0);
+
+    char *realName = NULL;
+    unsigned int numBadLines = 0;
+    struct stat filestat;
+
+    psTrace("psModules.config", 3, "Loading %s configuration from file %s\n",
+            description, name);
+
+    uid_t uid = getuid();
+    gid_t gid = getgid();
+
+    // we try: name, path[0]/name, path[1]/name, ...
+    // find the first existing entry in the path (starting with the bare name)
+    realName = psStringCopy (name);
+    psTrace (__func__, 5, "trying %s\n", realName);
+
+    int status = stat (realName, &filestat);
+    if (status == 0) {
+        if ((uid == filestat.st_uid) && (filestat.st_mode & S_IRUSR))
+            goto found;
+        if ((gid == filestat.st_gid) && (filestat.st_mode & S_IRGRP))
+            goto found;
+        if (filestat.st_mode & S_IROTH)
+            goto found;
+    }
+    psFree (realName);
+
+    if (configPath == NULL) {
+        psError(PS_ERR_IO, true, "Cannot find %s configuration file (%s) in path\n", description, name);
+        return false;
+    }
+
+    for (int i = 0; i < configPath->n; i++) {
+        realName = psStringCopy (configPath->data[i]);
+        psStringAppend (&realName, "/%s", name);
+        psTrace (__func__, 5, "trying %s\n", realName);
+
+        status = stat (realName, &filestat);
+        if (status == 0) {
+            if ((uid == filestat.st_uid) && (filestat.st_mode & S_IRUSR))
+                goto found;
+            if ((gid == filestat.st_gid) && (filestat.st_mode & S_IRGRP))
+                goto found;
+            if (filestat.st_mode & S_IROTH)
+                goto found;
+        }
+        psFree (realName);
+    }
+
+    psError(PS_ERR_IO, true, "Cannot find %s configuration file %s in path\n", description, name);
+    return false;
+
+found:
+    *config = psMetadataConfigRead(NULL, &numBadLines, realName, true);
+    if (numBadLines > 0) {
+        psError(PS_ERR_IO, false, "%d bad lines in %s configuration file (%s)",
+                numBadLines, description, realName);
+        psFree (realName);
+
+        return false;
+    }
+    if (!*config) {
+        psError(PS_ERR_IO, true, "Unable to read %s configuration from %s",
+                description, realName);
+        psFree (realName);
+        return false;
+    }
+
+    psFree (realName);
+    return true;
+}
+
+
+/******************************************************************************
+pmConfigRead(**site, **camera, **recipe, *argc, **argv, char *recipeName)
+ 
+XXX: The log/trace command line options (as processed by psArgumentVerbosity)
+must take precedence override the values set here.  This must be, somehow,
+coded.
+ 
+XXX: Must load camera and recipe configuration if specified in the command
+ line.
+ *****************************************************************************/
+pmConfig *pmConfigRead(
+    int *argc,
+    char **argv)
+{
+    PS_ASSERT_PTR_NON_NULL(argc, NULL);
+    PS_ASSERT_INT_POSITIVE(*argc, NULL);
+    PS_ASSERT_PTR_NON_NULL(argv, NULL);
+
+    pmConfig *config = pmConfigAlloc(argc, argv); // The configuration, containing site, camera and recipes
+
+    //
+    // The following section of code attempts to determine which file is
+    // the configuration file.  At the end of this code block, the siteName
+    // variable will contain the name of the configuration file.
+    //
+    char *siteName = NULL;
+    //
+    // First, try command line
+    //
+    psS32 argNum = psArgumentGet(*config->argc, config->argv, "-site");
+    if (argNum != 0) {
+        //
+        // We remove the "-site" argument from argv.  Then
+        // we look for the next argument, which should be the filename, and
+        // remove it as well.
+        //
+        psArgumentRemove(argNum, config->argc, config->argv);
+        if (argNum >= *config->argc) {
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "-site command-line switch provided without the required filename --- ignored.\n");
+        } else {
+            siteName = psStringCopy(config->argv[argNum]);
+            psArgumentRemove(argNum, config->argc, config->argv);
+        }
+    }
+    //
+    // Next, try environment variable
+    //
+    if (!siteName) {
+        siteName = getenv(PS_SITE);
+        if (siteName) {
+            siteName = psStringCopy (siteName);
+        }
+    }
+
+    //
+    // Last chance is ~/.ipprc
+    //
+    if (!siteName) {
+        char *home = getenv("HOME");
+        siteName = psStringCopy(home);
+        psStringAppend(&siteName, "/%s", PS_DEFAULT_SITE);
+    }
+
+
+    // We have the configuration filename; now we read and parse the config
+    // file and store in psMetadata struct site.
+    //
+
+    if (!readConfig(&config->site, siteName, "site")) {
+        psFree(config);
+        return NULL;
+    }
+
+    // define the config-file search path (configPath).  Ensure that
+    // it contains the directory where we found the config file in
+    // the first place
+    if (configPath) {
+        pmConfigDone();
+    }
+
+    psString siteNameDir = psStringCopy(dirname(siteName));
+    psFree(siteName);
+
+    psString path = psMetadataLookupStr(NULL, config->site, "PATH");
+    if (path) {
+        psStringAppend(&path, ":%s", siteNameDir);
+    } else {
+        path = psMemIncrRefCounter(siteNameDir);
+    }
+    pmConfigSet(path);
+    psFree(siteNameDir);
+
+    // Next, we do a similar thing for the camera configuration file.  The
+    // file is read and parsed into psMetadata struct "camera".
+    //
+    argNum = psArgumentGet(*config->argc, config->argv, "-camera");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, config->argc, config->argv);
+        if (argNum >= *config->argc) {
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "-camera command-line switch provided without the required camera or filename --- "
+                     "ignored.\n");
+        } else {
+            char *cameraFile = config->argv[argNum]; // The camera configuration file to read
+
+            // look for a symbolic camera name in the CAMERAS metadata
+            // otherwise fall back to trying the -camera option as a literal
+            // filename
+            bool mdok = true;           // Status of MD lookup
+            psMetadata *cameras = psMetadataLookupMD(&mdok, config->site, "CAMERAS");
+            if (mdok && cameras) {
+                char *newFile = psMetadataLookupStr(NULL, cameras, cameraFile); // The filename
+                if (newFile) {
+                    cameraFile = newFile;
+                }
+            } else {
+                psError(PS_ERR_IO, false, "Unable to find CAMERAS in site configuration.\n");
+                psFree(config);
+                return NULL;
+            }
+
+            readConfig(&config->camera, cameraFile, "camera");
+            psArgumentRemove(argNum, config->argc, config->argv);
+        }
+    }
+
+    // Load the recipes from the camera file, if appropriate
+    if(!pmConfigReadRecipes(config)) {
+        psError(PS_ERR_IO, false, "Failed to read recipes from camera file");
+        psFree(config);
+        return NULL;
+    }
+
+    //
+    // We now have the config, camera, and recipe files parsed and stored in
+    // metadata.  Now, we can look into the site configuration and do
+    // the required stuff.
+    //
+    bool mdok = true;   // Status of MD lookup result
+
+    //
+    // If TIME is specified in the configuration file, then we must initialize
+    // with a call to psTimeInitialize.
+    //
+    psString timeName = psMetadataLookupStr(&mdok, config->site, "TIME");
+    if (mdok && timeName) {
+        psTrace("psModules.config", 7, "Initialising psTime with file %s\n", timeName);
+        // XXX: PAP had a call to psLibInit is PRODUCTION not set.  Why?
+        psTimeInitialize(timeName);
+    }
+
+
+    //
+    // If LOGLEVEL is specified in the configuration file, then we must initialize
+    // with a call to psLogSetLevel().
+    //
+    int logLevel = psMetadataLookupS32(&mdok, config->site, "LOGLEVEL");
+    if (mdok && logLevel >= 0) {
+        psTrace("psModules.config", 7, "Setting log level to %d\n", logLevel);
+        psLogSetLevel(logLevel);
+    }
+
+
+    //
+    // If LOGFORMAT is specified in the configuration file, then we must initialize
+    // with a call to psLogSetFormat().
+    //
+    psString logFormat = psMetadataLookupStr(&mdok, config->site, "LOGFORMAT");
+    if (mdok && logFormat) {
+        psTrace("psModules.config", 7, "Setting log format to %s\n", logFormat);
+        psLogSetFormat(logFormat);
+    }
+
+
+    argNum = psArgumentGet(*config->argc, config->argv, "-log");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, config->argc, config->argv);
+        if (argNum >= *config->argc) {
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "-log command-line switch provided without the required log destination --- ignored.\n");
+        } else {
+            if (!psLogSetDestination(psMessageDestination(config->argv[argNum]))) {
+                psLogMsg(__func__, PS_LOG_WARN, "Unable to set log destination to %s\n",
+                         config->argv[argNum]);
+            }
+            psArgumentRemove(argNum, config->argc, config->argv);
+        }
+    } else {
+        //
+        // If LOGDEST is specified in the configuration file, then we must initialize
+        // with a call to psLogSetDestination().
+        // XXX: This is not spec'ed in the SDRS.
+        //
+        psString logDest = psMetadataLookupStr(&mdok, config->site, "LOGDEST");
+        if (mdok && logDest && strlen(logDest) > 0) {
+            // XXX: Only stdout and stderr are provided for now; this section should be
+            // expanded in the future to do files, and perhaps even sockets.
+            if (!psLogSetDestination(psMessageDestination(logDest))) {
+                psLogMsg(__func__, PS_LOG_WARN, "Unable to set log destination to %s\n",
+                         config->argv[argNum]);
+            }
+        }
+    }
+
+    //
+    // If TRACE is specified in the configuration file, then we must initialize
+    // with a call to psTraceSetLevel().
+    // XXX: This is not spec'ed in the SDRS.
+    //
+    psMetadata *trace = psMetadataLookupMD(&mdok, config->site, "TRACE");
+    if (mdok && trace) {
+        psMetadataIterator *traceIter = psMetadataIteratorAlloc(trace, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *traceItem = NULL; // Item from MD iteration
+        while ((traceItem = psMetadataGetAndIncrement(traceIter))) {
+            if (traceItem->type != PS_DATA_S32) {
+                psLogMsg(__func__, PS_LOG_WARN, "The level for trace component %s is not of type S32 (%x)\n",
+                         traceItem->name, traceItem->type);
+                continue;
+            }
+            psTrace("psModules.config", 7, "Setting trace level for %s to %d\n", traceItem->name, traceItem->data.S32);
+            psTraceSetLevel(traceItem->name, traceItem->data.S32);
+        }
+        psFree(traceIter);
+    }
+    //
+    // If TRACEFORMAT is specified in the configuration file, then we must
+    // initialize with a call to psTraceSetFormat().
+    //
+    psString traceFormat = psMetadataLookupStr(&mdok, config->site, "TRACEFORMAT");
+    if (mdok && traceFormat) {
+        psTrace("psModules.config", 7, "Setting trace format to %s\n", traceFormat);
+        psTraceSetFormat(traceFormat);
+    }
+
+    //
+    // If LOGDEST is specified in the configuration file, then we must initialize
+    // with a call to psLogSetDestination().
+    // XXX: This is not spec'ed in the SDRS.
+    //
+    psString traceDest = psMetadataLookupStr(&mdok, config->site, "TRACEDEST");
+    if (mdok && traceDest && strlen(traceDest) > 0) {
+        psTrace("psModules.config", 7, "Setting trace destination to %s\n", traceDest);
+        // XXX: Only stdout and stderr are provided for now; this section should be
+        // expanded in the future to do files, and perhaps even sockets.
+        if (!psTraceSetDestination(psMessageDestination(traceDest))) {
+            psLogMsg(__func__, PS_LOG_WARN, "Unable to set trace destination to %s\n",
+                     config->argv[argNum]);
+        }
+    }
+
+    //
+    // Allow command line options to override defaults for logging.
+    // XXX: Is it appropriate to use the ArgVerbosity function for this?
+    //   A: it removes the options from the command line.
+    //   B: will the pmConfigRead function always be called on initialization.
+    //
+    psS32 saveLogLevel = psLogGetLevel();
+    psArgumentVerbosity(config->argc, config->argv);
+    // XXX: substitute the string for the default log level for "2".
+    if (2 == psLogGetLevel()) {
+        psLogSetLevel(saveLogLevel);
+    }
+
+    psErrorClear();   // we may have failed to find some items in the metadata
+
+    return config;
+}
+
+
+bool pmConfigValidateCameraFormat(
+    const psMetadata *cameraFormat,
+    const psMetadata *header)
+{
+    PS_ASSERT_PTR_NON_NULL(cameraFormat, false);
+    PS_ASSERT_PTR_NON_NULL(header, false);
+
+    // Read the rule for that camera format
+    bool mdStatus = true;
+    psMetadata *rule = psMetadataLookupMD(&mdStatus, cameraFormat, "RULE");
+    if (! mdStatus || ! rule) {
+        psLogMsg(__func__, PS_LOG_WARN, "Unable to read rule for camera.\n");
+        return false;
+    }
+
+    // Apply the rules
+    psMetadataIterator *ruleIter = psMetadataIteratorAlloc(rule, PS_LIST_HEAD, NULL); // Rule iterator
+    psMetadataItem *ruleItem = NULL;    // Item from the metadata
+    while ((ruleItem = psMetadataGetAndIncrement(ruleIter))) {
+        // Check for the existence of the rule
+        psMetadataItem *headerItem = psMetadataLookup((psMetadata*)header, ruleItem->name);
+        if (! headerItem) {
+            // It doesn't have a required header keyword, so it's not it
+            psFree(ruleIter);
+            psTrace("psModules.config", 5, "Can't find %s\n", ruleItem->name);
+            return false;
+        }
+
+        // Check to see if the rule works
+        if (! psMetadataItemCompare(headerItem, ruleItem)) {
+            psTrace("psModules.config", 5, "%s doesn't match.\n", ruleItem->name);
+            psFree(ruleIter);
+            return false;
+        }
+    } // Iterating through the RULEs
+
+    psFree(ruleIter);
+    return true;
+}
+
+
+static bool formatFromHeader(psMetadata **format, // Format to return
+                             psMetadata *camera, // Camera configuration
+                             const psMetadata *header, // FITS header
+                             const char *cameraName // Name of camera
+                            )
+{
+    assert(format);
+    assert(camera);
+    assert(header);
+    assert(cameraName && strlen(cameraName) > 0);
+
+    bool result = false;                // Did we find the first match?
+
+    // Read the list of formats
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS"); // List of formats
+    if (!mdok || !formats) {
+        psLogMsg(__func__, PS_LOG_WARN, "Unable to find list of FORMATS in camera %s --- ignored\n",
+                 cameraName);
+        return false;
+    }
+    // Iterate over the formats
+    psMetadataIterator *formatsIter = psMetadataIteratorAlloc(formats, PS_LIST_HEAD, NULL);
+    psMetadataItem *formatsItem = NULL; // Item from formats
+    while ((formatsItem = psMetadataGetAndIncrement(formatsIter))) {
+        if (formatsItem->type != PS_DATA_STRING) {
+            psLogMsg(__func__, PS_LOG_WARN, "In camera %s, camera format %s is not of type STR --- "
+                     "ignored.\n", cameraName, formatsItem->name);
+            continue;
+        }
+        psTrace("psModules.config", 5, "Reading camera format for %s...\n", formatsItem->name);
+        psMetadata *testFormat = NULL;  // Format to test against what we've got
+        if (!readConfig(&testFormat, formatsItem->data.V, formatsItem->name)) {
+            psLogMsg(__func__, PS_LOG_WARN, "Trouble reading reading camera format %s --- ignored.\n",
+                     formatsItem->name);
+            psFree(testFormat);
+            continue;
+        }
+
+        if (pmConfigValidateCameraFormat(testFormat, header)) {
+            if (!*format) {
+                psLogMsg(__func__, PS_LOG_INFO, "Camera %s, format %s matches header.\n", cameraName,
+                         formatsItem->name);
+                *format = psMemIncrRefCounter(testFormat);
+                result = true;
+            } else {
+                psLogMsg(__func__, PS_LOG_WARN, "Camera %s, format %s also matches header --- ignored.\n",
+                         cameraName, formatsItem->name);
+            }
+        }
+        psFree(testFormat);
+    }
+    psFree(formatsIter);
+
+    return result;
+}
+
+// Work out what camera we have, based on the FITS header and a set of rules specified in the IPP
+// configuration; return the camera configuration and format
+psMetadata *pmConfigCameraFormatFromHeader(
+    pmConfig *config,                   // The configuration
+    const psMetadata *header            // The FITS header
+)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(header, NULL);
+
+    psMetadata *format = NULL;          // The winning format
+    bool mdok = false;                  // Metadata lookup status
+
+    // If we don't know what sort of camera we have, we try all that we know
+    if (! config->camera) {
+        psMetadata *cameras = psMetadataLookupMD(&mdok, config->site, "CAMERAS");
+        if (! mdok) {
+            psError(PS_ERR_IO, true, "Unable to find CAMERAS in the configuration.");
+            return false;
+        }
+
+        // Iterate over the cameras
+        psMetadataIterator *camerasIter = psMetadataIteratorAlloc(cameras, PS_LIST_HEAD, NULL);
+        psMetadataItem *camerasItem = NULL; // Item from the metadata
+        while ((camerasItem = psMetadataGetAndIncrement(camerasIter))) {
+            // Open the camera information
+            psTrace("psModules.config", 3, "Inspecting camera %s (%s)\n", camerasItem->name, camerasItem->comment);
+            if (camerasItem->type != PS_DATA_STRING) {
+                psLogMsg(__func__, PS_LOG_WARN, "Camera configuration for %s in CAMERAS is not of type STR "
+                         "--- ignored.\n", camerasItem->name);
+                continue;
+            }
+
+            psTrace("psModules.config", 5, "Reading camera configuration for %s...\n", camerasItem->name);
+            psMetadata *testCamera = NULL; // Camera to test against what we've got:
+
+            if (!readConfig(&testCamera, camerasItem->data.V, camerasItem->name)) {
+                psLogMsg(__func__, PS_LOG_WARN, "Trouble reading reading camera configuration %s --- "
+                         "ignored.\n", camerasItem->name);
+                psFree(testCamera);
+                continue;
+            }
+
+            if (formatFromHeader(&format, testCamera, header, camerasItem->name)) {
+                config->camera = psMemIncrRefCounter(testCamera);
+                config->cameraName = psStringCopy(camerasItem->name);
+            }
+            psFree(testCamera);
+        } // Done looking at all cameras
+        psFree(camerasIter);
+
+        if (! config->camera) {
+            psError(PS_ERR_IO, false, "Unable to find a camera that matches input FITS header!");
+            return NULL;
+        }
+
+        // Now we have the camera, we can read the recipes
+        pmConfigReadRecipes(config);
+
+        return format;
+    }
+
+    // Otherwise, try the specific camera
+    if (!formatFromHeader(&format, config->camera, header, config->cameraName)) {
+        psError(PS_ERR_IO, true, "Unable to find a format with the specified camera (%s) that matches the "
+                "given header.\n", config->cameraName);
+        return NULL;
+    }
+    return format;
+}
+
+// Return the requested camera configuration
+psMetadata *pmConfigCameraByName(
+    pmConfig *config,                   // The configuration
+    const char *cameraName   // The camera name header
+)
+{
+    PS_ASSERT_PTR_NON_NULL(cameraName, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    psMetadata *cameras = psMetadataLookupMD(NULL, config->site, "CAMERAS");
+    if (!cameras) {
+        psError(PS_ERR_IO, true, "Unable to find CAMERAS in the configuration.");
+        return NULL;
+    }
+
+    char *cameraPath = psMetadataLookupStr(NULL, cameras, cameraName);
+    if (!cameraPath) {
+        psError(PS_ERR_IO, true, "Unable to find requested CAMERA in the configuration.");
+        return NULL;
+    }
+
+    psMetadata *camera = NULL; // Camera to test against what we've got:
+
+    if (!readConfig(&camera, cameraPath, cameraName)) {
+        psLogMsg(__func__, PS_LOG_WARN, "Trouble reading reading camera configuration %s", cameraName);
+        psFree(camera);
+        return NULL;
+    }
+
+    return camera;
+}
+
+
+// Load the recipes
+static bool loadRecipes(pmConfig *config, // The configuration into which to read the recipes
+                        psMetadata *source, // The source configuration, from which to read the filenames
+                        p_pmRecipeSource sourceType, // The source type
+                        const char *sourceName // The name of the source, for error messages
+                       )
+{
+    assert(config);
+
+    if (!source) {
+        #if 0
+        psLogMsg(__func__, PS_LOG_WARN, "The %s has not been read --- cannot read recipes from this "
+                 "location.\n", sourceName);
+        #endif
+
+        config->recipesRead &= ~sourceType;
+        return false;
+    }
+
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *recipes = psMetadataLookupMD(&mdok, source, "RECIPES"); // The list of recipes
+    if (!mdok || !recipes) {
+        psLogMsg(__func__, PS_LOG_WARN, "RECIPES in the %s is not of type METADATA --- ignored\n",
+                 sourceName);
+        config->recipesRead &= ~sourceType;
+        return false;
+    }
+
+    // Copy the filenames to the target from the "RECIPES" in the source.
+    // We could use psMetadataCopy for this, but it's better to check that everything's of the correct type.
+    // If it's not of the correct type, we can tell the user which file it's in, so they can find it easier.
+    {
+        psMetadataIterator *recipesIter = psMetadataIteratorAlloc(recipes, PS_LIST_HEAD, NULL); // Iterator
+        psMetadataItem *fileItem = NULL;    // MD item containing the filename, from recipe iteration
+        while ((fileItem = psMetadataGetAndIncrement(recipesIter)))
+        {
+            if (fileItem->type != PS_DATA_STRING) {
+                psLogMsg(__func__, PS_LOG_WARN, "Recipe %s from %s is not of type STR --- ignored.\n",
+                         fileItem->name, sourceName);
+                continue;
+            }
+
+            // Check to see if it's currently defined
+            int check = psMetadataLookupS32(&mdok, config->recipesSource, fileItem->name);
+            if (mdok && check > sourceType) {
+                // It's already defined with a higher priority
+                continue;
+            }
+            psString comment = psStringCopy("Recipe added at ");
+            psStringAppend(&comment, "%s from %s", sourceName, (char*)fileItem->data.V);
+            psMetadataAddS32(config->recipesSource, PS_LIST_TAIL, fileItem->name,
+                             PS_META_REPLACE, comment, sourceType);
+            psFree(comment);
+
+            // Read the recipe
+            psMetadata *recipe = NULL;      // Recipe from file
+            if (readConfig(&recipe, fileItem->data.V, "recipe")) {
+                psMetadataAdd(config->recipes, PS_LIST_TAIL, fileItem->name,
+                              PS_DATA_METADATA | PS_META_REPLACE, fileItem->comment, recipe);
+            } else {
+                psError(PS_ERR_IO, false, "Failed to read recipe");
+
+                psFree(recipe);  // Drop reference
+                psFree(recipesIter);
+
+                return false;
+            }
+            psFree(recipe);                 // Drop reference
+        }
+        psFree(recipesIter);
+    }
+    config->recipesRead |= sourceType;
+
+    {
+        // Having read that, we now need to check to see if any symbolic links need to be resolved
+        psMetadataIterator *recipesIter = psMetadataIteratorAlloc(config->recipesSource, PS_LIST_HEAD, NULL);
+        psMetadataItem *sourceItem = NULL;  // Item containing source, from iteration
+        while ((sourceItem = psMetadataGetAndIncrement(recipesIter))) {
+            assert(sourceItem->type == PS_TYPE_S32); // It should be this type: we put it in ourselves
+            if (sourceItem->data.S32 == P_PM_RECIPE_SOURCE_SYMBOLIC) {
+                const char *linkName = sourceItem->comment; // The name of the link
+                psMetadata *linkSource = psMetadataLookupMD(&mdok, config->recipes, linkName); // The source
+                if (!mdok || !linkSource) {
+                    // Can't yet resolve it
+                    continue;
+                }
+                psString comment = NULL;
+                psStringAppend(&comment, "Symbolic link from %s", linkName);
+                psMetadataAdd(config->recipes, PS_LIST_TAIL, sourceItem->name,
+                              PS_DATA_METADATA | PS_META_REPLACE, comment, linkSource);
+                psFree(comment);
+            }
+        }
+        psFree(recipesIter);
+    }
+    return true;
+}
+
+
+bool pmConfigReadRecipes(pmConfig *config
+                        )
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+
+    if (!config->recipes) {
+        config->recipes = psMetadataAlloc();
+    }
+
+    bool mdok = true;                   // Status of MD lookup
+
+    // Read the recipe file names from the site configuration and camera configuration
+    if (config->site && !(config->recipesRead & P_PM_RECIPE_SOURCE_SITE)) {
+        if(!loadRecipes(config, config->site, P_PM_RECIPE_SOURCE_SITE, "site configuration")) {
+            psError(PS_ERR_IO, false, "Failed to read recipes from site config");
+            #if 0     // see comment at end of routine
+
+            return false;
+            #endif
+
+        }
+    }
+    if (config->camera && !(config->recipesRead & P_PM_RECIPE_SOURCE_CAMERA)) {
+        if (!loadRecipes(config, config->camera, P_PM_RECIPE_SOURCE_CAMERA, "camera configuration")) {
+            psError(PS_ERR_IO, false, "Failed to read recipes from camera config");
+            #if 0     // see comment at end of routine
+
+            return false;
+            #endif
+
+        }
+    }
+
+    if (!(config->recipesRead & P_PM_RECIPE_SOURCE_CL)) {
+        // Go through the command-line arguments
+        int argNum; // Argument number for "-recipe"
+        while ((argNum = psArgumentGet(*config->argc, config->argv, "-recipe")) > 0) {
+            psArgumentRemove(argNum, config->argc, config->argv);
+            if (argNum + 1 >= *config->argc) {
+                psLogMsg(__func__, PS_LOG_WARN, "-recipe command-line switch provided without the "
+                         "required recipe and source --- ignored.\n");
+                if (argNum == *config->argc) {
+                    // Remove the single last argument (we required two, they gave us one)
+                    psArgumentRemove(argNum, config->argc, config->argv);
+                }
+                continue;
+            }
+
+            const char *recipeName = psStringCopy(config->argv[argNum]); // Name of the recipe
+            psArgumentRemove(argNum, config->argc, config->argv);
+            const char *recipeSource = psStringCopy(config->argv[argNum]); // Source of the recipe
+            psArgumentRemove(argNum, config->argc, config->argv);
+
+            // Command-line has the highest priority, so we don't have to check to see if it's already there
+
+            // Is the source a symbolic reference?
+            psMetadata *extant = psMetadataLookupMD(&mdok, config->recipes, recipeSource); // Does it exist?
+            if (mdok && extant) {
+                psString comment = psStringCopy("Recipe added from command line as symbolic link to ");
+                psStringAppend(&comment, "%s", recipeSource);
+                psMetadataAdd(config->recipes, PS_LIST_TAIL, recipeName, PS_DATA_METADATA | PS_META_REPLACE,
+                              comment, extant);
+                psFree(comment);
+                // Put the source in the comment, so we can retrieve it again if we need it
+                psMetadataAddS32(config->recipesSource, PS_LIST_TAIL, recipeName, PS_META_REPLACE,
+                                 recipeSource, P_PM_RECIPE_SOURCE_SYMBOLIC);
+
+            } else if (access(recipeSource, R_OK) == 0) {
+                // The source is a file
+                psMetadata *recipe = NULL;      // Recipe from file
+                if (readConfig(&recipe, recipeSource, "recipe")) {
+                    psString comment = psStringCopy("Recipe added at command line from ");
+                    psStringAppend(&comment, "%s", recipeSource);
+                    psMetadataAdd(config->recipes, PS_LIST_TAIL, recipeName,
+                                  PS_DATA_METADATA | PS_META_REPLACE, comment, recipe);
+                    psFree(comment);
+                    psFree(recipe);                 // Drop reference
+                    psMetadataAddS32(config->recipesSource, PS_LIST_TAIL, recipeName, PS_META_REPLACE,
+                                     "Recipe added from command line", P_PM_RECIPE_SOURCE_CL);
+                }
+            } else {
+                // Assume it's a symbolic reference to something that's not yet read in
+                psMetadataAddS32(config->recipesSource, PS_LIST_TAIL, recipeName, PS_META_REPLACE,
+                                 recipeSource, P_PM_RECIPE_SOURCE_SYMBOLIC);
+            }
+            psFree(recipeName);
+            psFree(recipeSource);
+        } // Iterating through the command-line arguments
+        config->recipesRead |= P_PM_RECIPE_SOURCE_CL;
+    }
+    /*
+     * We may have seen real errors, but we also get false status returned by valid conditions (e.g. asking a
+     * file for a recipe when none was provided).  For now we'll never signal an error, but this should
+     * be reconsidered ASAP.
+     */
+    psErrorClear();
+    return true;
+}
+
+/******************************************************************************
+pmConfigDB(*site)
+ 
+XXX: this should allow the option of having NO database server, if chosen by config
+XXX: What should we use for the Database namespace in the call to psDBInit()?
+This is currently NULL.
+ *****************************************************************************/
+
+psDB *pmConfigDB(
+    pmConfig *config                    // Configuration
+)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->site, NULL);
+
+    #ifdef OMIT_PSDB
+
+    return NULL;
+    #else
+
+    psBool mdStatus01 = false;
+    psBool mdStatus02 = false;
+    psBool mdStatus03 = false;
+    psBool mdStatus04 = false;
+    psBool mdStatus05 = false;
+
+    // XXX leaky strings
+    psString dbServer = psMetadataLookupStr(&mdStatus01, config->site, "DBSERVER");
+    psString dbUsername = psMetadataLookupStr(&mdStatus02, config->site, "DBUSER");
+    psString dbPassword = psMetadataLookupStr(&mdStatus03, config->site, "DBPASSWORD");
+    psString dbName = psMetadataLookupStr(&mdStatus04, config->site, "DBNAME");
+    psS32 dbPort = psMetadataLookupS32(&mdStatus05, config->site, "DBPORT");
+    if (!mdStatus05)
+        dbPort = 0;
+    if (!(mdStatus01 & mdStatus02 & mdStatus03 & mdStatus04)) {
+        psLogMsg(__func__, PS_LOG_WARN, "Could not determine database server name, userID, and password from site metadata.\n");
+        return(NULL);
+    }
+
+    return(psDBInit(dbServer, dbUsername, dbPassword, dbName, dbPort));
+    #endif
+}
+
+
+bool pmConfigConformHeader(psMetadata *header, // Header to conform
+                           const psMetadata *format // Camera format
+                          )
+{
+    PS_ASSERT_PTR_NON_NULL(header, false);
+    PS_ASSERT_PTR_NON_NULL(format, false);
+
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *rules = psMetadataLookupMD(&mdok, format, "RULE"); // How to identify this format
+    if (!mdok || !rules) {
+        psError(PS_ERR_IO, true, "Unable to find RULE in camera format.\n");
+        return false;
+    }
+
+    psMetadataIterator *rulesIter = psMetadataIteratorAlloc(rules, PS_LIST_HEAD, NULL); // Iterator for rules
+    psMetadataItem *rulesItem = NULL;   // Item from iteration
+    while ((rulesItem = psMetadataGetAndIncrement(rulesIter))) {
+        psMetadataItem *newItem = psMetadataItemCopy(rulesItem); // Copy of item
+        psMetadataAddItem(header, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+        psFree(newItem);                // Drop reference
+    }
+    psFree(rulesIter);
+
+    return true;
+}
+
+// given the 'file' and 'list' words, find the arguments associated with these words
+// and interpret them as lists of files.  return an array of the resulting filenames.
+psArray *pmConfigFileSets(pmConfig *config, char *file, char *list)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->argc, NULL);
+    PS_ASSERT_INT_NONNEGATIVE(*config->argc, NULL);
+    PS_ASSERT_PTR_NON_NULL(config->argv, NULL);
+    PS_ASSERT_PTR_NON_NULL(file, NULL);
+    PS_ASSERT_INT_POSITIVE(strlen(file), NULL);
+    PS_ASSERT_PTR_NON_NULL(list, NULL);
+    PS_ASSERT_INT_POSITIVE(strlen(list), NULL);
+
+    int Narg;
+
+    // we load all input files onto a psArray, to be parsed later
+    psArray *input = psArrayAlloc (16);
+    input->n = 0;
+
+    // load the list of filenames the supplied file
+    // maybe a comma-separated list of words
+    // each word may be a glob: "file*.fits"
+    if ((Narg = psArgumentGet (*config->argc, config->argv, file))) {
+
+        // select the word after 'file' and split by comma
+        psArgumentRemove (Narg, config->argc, config->argv);
+        psArray *words = psStringSplitArray (config->argv[Narg], ",", true);
+        psArgumentRemove (Narg, config->argc, config->argv);
+
+        // parse the word as a glob
+        glob_t globList;
+        for (int i = 0; i < words->n; i++) {
+            globList.gl_offs = 0;
+            glob (words->data[i], 0, NULL, &globList);
+
+            if (globList.gl_pathc == 0) {
+                psError(PS_ERR_IO, true, "No match for %s", (char *)words->data[i]);
+                return input;
+            }
+
+            for (int j = 0; j < globList.gl_pathc; j++) {
+                char *filename = psStringCopy (globList.gl_pathv[j]);
+                psArrayAdd (input, 16, filename);
+                psFree (filename);
+            }
+        }
+        psFree (words);
+    }
+
+    // load the list from the supplied text file
+    if ((Narg = psArgumentGet (*config->argc, config->argv, list))) {
+        int nItems;
+        char line[1024]; // XXX limits the list lines to 1024 chars
+        char word[1024];
+        char *filename;
+
+        psArgumentRemove (Narg, config->argc, config->argv);
+        FILE *f = fopen (config->argv[Narg], "r");
+        if (f == NULL) {
+            psAbort ("psphot", "unable to open specified list file");
+        }
+        while (fgets (line, 1024, f) != NULL) {
+            nItems = sscanf (line, "%s", word);
+            switch (nItems) {
+            case 0:
+                break;
+            case 1:
+                filename = psStringCopy (word);
+                psArrayAdd (input, 16, filename);
+                psFree (filename);
+                break;
+            default:
+                // rigid format, no comments allowed?
+                psAbort ("pmConfig", "error parsing input list file");
+                break;
+            }
+        }
+        psArgumentRemove (Narg, config->argc, config->argv);
+    }
+
+    return input;
+}
+
+bool pmConfigFileSetsMD (psMetadata *metadata, pmConfig *config, char *name, char *file, char *list)
+{
+    PS_ASSERT_PTR_NON_NULL(metadata, false);
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(name, false);
+    PS_ASSERT_INT_POSITIVE(strlen(name), false);
+    PS_ASSERT_PTR_NON_NULL(file, false);
+    PS_ASSERT_INT_POSITIVE(strlen(file), false);
+    PS_ASSERT_PTR_NON_NULL(list, false);
+    PS_ASSERT_INT_POSITIVE(strlen(list), false);
+
+    psErrorClear();   // pmConfigFileSets may or may not call psError, so
+    // if files->n == 0 we'll want to call psError(..., false, ...)
+    psArray *files = pmConfigFileSets(config, file, list);
+    if (files->n == 0) {
+        // psError(PS_ERR_IO, false, "pmConfigFileSets failed to find %s in the argument list", name);
+        psFree (files);
+        return false;
+    }
+
+    psMetadataAddPtr(metadata, PS_LIST_TAIL, name,  PS_DATA_ARRAY, "", files);
+    psFree (files);
+    return true;
+}
Index: /tags/RHL_v1_1/psModules/src/config/pmConfig.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/config/pmConfig.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/config/pmConfig.h	(revision 9533)
@@ -0,0 +1,145 @@
+/** @file  pmConfig.h
+ *
+ *  @author PAP, IfA
+ *
+ *  @version $Revision: 1.14 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-10-09 20:19:25 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+#ifndef PM_CONFIG_H
+#define PM_CONFIG_H
+
+#include "pslib.h"
+
+// What recipe sources have been read so far?
+typedef enum {
+    P_PM_RECIPE_SOURCE_NONE        = 0x00, // None yet
+    P_PM_RECIPE_SOURCE_SITE        = 0x01, // Site configuration
+    P_PM_RECIPE_SOURCE_CAMERA      = 0x02, // Camera configuration
+    P_PM_RECIPE_SOURCE_CL          = 0x04, // Command-line
+    P_PM_RECIPE_SOURCE_SYMBOLIC    = 0x14, // Symbolic link, specified on command-line
+    P_PM_RECIPE_SOURCE_ALL         = 0xff  // All sources
+} p_pmRecipeSource;
+
+// Configuration information
+typedef struct
+{
+    psMetadata *site;                   // Site configuration
+    psMetadata *camera;                 // Camera specification
+    const char *cameraName;             // Camera name
+    psMetadata *recipes;                // Recipes for processing
+    psMetadata *arguments;              // Processed command-line arguments
+    psMetadata *files;                  // pmFPAfiles used for analysis
+    psDB *database;                     // Database handle
+    int *argc;                          // Number of command-line arguments
+    char **argv;                        // Command-line arguments (raw version)
+    // Private members
+    p_pmRecipeSource recipesRead;       // Which recipe sources have been read
+    psMetadata *recipesSource;          // Where each recipe came from
+}
+pmConfig;
+
+pmConfig *pmConfigAlloc(int *argc,      // Number of command-line arguments
+                        char **argv     // Command-line arguments
+                       );
+
+void pmConfigSet(const char *path); // set the configPath
+
+// Free static variables
+void pmConfigDone(void);
+
+/** pmConfigRead
+ *
+ * pmConfigRead shall load the site configuration (according to the above rule
+ * for determining the source). The camera configuration shall also be loaded if
+ * it is specified on the command line (argc, argv); otherwise it shall be set to
+ * NULL. The recipe shall also be loaded from the command line (if specified) or,
+ * if the camera configuration has been loaded, from the camera configuration and
+ * recipe specification therein (see below). In dealing with the command line
+ * parameters, the functions shall use the appropriate functions in psLib to
+ * retrieve and remove the relevant options from the argument list; this
+ * simplifies assignment of the mandatory arguments, since all the optional
+ * command line arguments are removed leaving only the mandatory arguments. The
+ * following psLib setups shall also be performed if they are specified in the
+ * site configuration:
+ *
+ */
+pmConfig *pmConfigRead(
+    int *argc,
+    char **argv);
+
+/** we need this elsewhere; make it public **/
+bool readConfig(
+    psMetadata **config,                // Config to output
+    const char *name,                   // Name of file
+    const char *description             // Description of file
+);
+
+/** pmConfigValidateCamera
+ *
+ * This function, used by pmConfigCameraFromHeader, shall return true if the
+ * FITS header matches the rule contained in the camera configuration (see
+ * x2.2.2.3); otherwise it shall return false.
+ *
+ */
+bool pmConfigValidateCameraFormat(
+    const psMetadata *cameraFormat,
+    const psMetadata *header);
+
+
+/** pmConfigCameraFromHeader
+ *
+ * pmConfigCameraFromHeader shall load the camera configuration based on the
+ * contents of the FITS header, using the list of known cameras contained in the
+ * site configuration. If more than one camera matches the FITS header, a warning
+ * shall be generated and the first matching camera returned.
+ *
+ */
+psMetadata *pmConfigCameraFormatFromHeader(
+    pmConfig *config,                   // The configuration
+    const psMetadata *header           // The FITS header
+);
+
+
+// find the camera given the name
+psMetadata *pmConfigCameraByName(
+    pmConfig *config,                   // The configuration
+    const char *cameraName                      // The camera name header
+);
+
+/** pmConfigRecipeFromCamera
+ *
+ * pmConfigRecipeFromCamera shall load the recipes from the list of known recipes contained in the camera
+ * configuration.
+ *
+ */
+bool pmConfigReadRecipes(pmConfig *config // Configuration
+                        );
+
+/** pmConfigDB
+ *
+ * pmConfigDB shall use the site configuration data to open a database handle.
+ * This is fairly straightforward at the moment, but will change when we beef up
+ * security. (TBD)
+ *
+ */
+psDB *pmConfigDB(
+    pmConfig *config                    // Configuration
+);
+
+/** pmConfigConformHeader
+ *
+ * Make the supplied header conform to the nominated camera format.
+ */
+bool pmConfigConformHeader(psMetadata *header, // Header to conform
+                           const psMetadata *format // Camera format
+                          );
+
+
+psArray *pmConfigFileSets(pmConfig *config, char *file, char *list);
+bool pmConfigFileSetsMD (psMetadata *metadata, pmConfig *config, char *name, char *file, char *list);
+
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/detrend/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/.cvsignore	(revision 9533)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/RHL_v1_1/psModules/src/detrend/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/Makefile.am	(revision 9533)
@@ -0,0 +1,39 @@
+noinst_LTLIBRARIES = libpsmodulesdetrend.la
+
+libpsmodulesdetrend_la_CPPFLAGS = $(SRCINC) $(PSMODULES_CFLAGS)
+libpsmodulesdetrend_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulesdetrend_la_SOURCES  = \
+	pmFlatField.c \
+	pmFlatNormalize.c \
+	pmFringeStats.c \
+	pmMaskBadPixels.c \
+	pmNonLinear.c \
+	pmSubtractBias.c \
+	pmDetrendDB.c \
+	pmShutterCorrection.c \
+	psPipe.c \
+	psIOBuffer.c
+
+#	pmSubtractSky.c
+
+pkginclude_HEADERS = \
+	pmFlatField.h \
+	pmFlatFieldErrors.h \
+	pmFlatNormalize.h \
+	pmFringeStats.h \
+	pmMaskBadPixelsErrors.h \
+	pmMaskBadPixels.h \
+	pmNonLinear.h \
+	pmSubtractBias.h \
+	pmDetrendDB.h \
+	pmShutterCorrection.h \
+	psPipe.h \
+	psIOBuffer.h
+
+#	pmSubtractSky.c
+
+EXTRA_DIST = \
+	pmFlatFieldErrors.dat \
+	pmMaskBadPixelsErrors.dat
+
+CLEANFILES = *~
Index: /tags/RHL_v1_1/psModules/src/detrend/pmDetrendDB.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmDetrendDB.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmDetrendDB.c	(revision 9533)
@@ -0,0 +1,272 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmFPA.h"
+#include "pmDetrendDB.h"
+#include "psPipe.h"
+#include "psIOBuffer.h"
+
+// ************* detrend select functions **************
+
+//
+static void pmDetrendSelectOptionsFree (pmDetrendSelectOptions *options)
+{
+
+    if (!options)
+        return;
+
+    psFree (options->camera);
+    psFree (options->filter);
+
+    return;
+}
+
+// define basic options for a new detrend database query
+pmDetrendSelectOptions *pmDetrendSelectOptionsAlloc(const char *camera, psTime time, pmDetrendType type)
+{
+
+    pmDetrendSelectOptions *options = psAlloc(sizeof(pmDetrendSelectOptions));
+    psMemSetDeallocator(options, (psFreeFunc) pmDetrendSelectOptionsFree);
+
+    // basic options required by every query
+    options->camera = psStringCopy (camera);
+    options->time   = time;
+    options->type   = type;
+
+    // these other options depend on the type of detrend data
+    options->filter = NULL;  //
+    options->exptime = -1.0; // the undefined value (safe since exptime >= 0)
+    options->airmass = -1.0; // the undefined value (safe since airmass >= 1)
+
+    return options;
+}
+
+static void pmDetrendSelectResultsFree (pmDetrendSelectResults *results)
+{
+
+    if (!results)
+        return;
+
+    psFree (results->detID);
+
+    return;
+}
+
+pmDetrendSelectResults *pmDetrendSelectResultsAlloc ()
+{
+
+    pmDetrendSelectResults *results = psAlloc(sizeof(pmDetrendSelectResults));
+    psMemSetDeallocator(results, (psFreeFunc) pmDetrendSelectResultsFree);
+
+    results->detID = NULL;
+    results->level = PM_FPA_LEVEL_NONE;
+
+    return results;
+}
+
+psString pmDetrendTypeToString (pmDetrendType type)
+{
+
+    # define DETREND_STRING_CASE(TTT) \
+case PM_DETREND_TYPE_##TTT: \
+    return psStringCopy (#TTT);
+
+    switch (type) {
+        DETREND_STRING_CASE (MASK);
+        DETREND_STRING_CASE (BIAS);
+        DETREND_STRING_CASE (DARK);
+        DETREND_STRING_CASE (FLAT);
+        DETREND_STRING_CASE (FLAT_CORRECTION);
+        DETREND_STRING_CASE (FRINGE_IMAGE);
+        DETREND_STRING_CASE (FRINGE_TABLE);
+    default:
+        return NULL;
+    }
+    return NULL;
+}
+
+// detselect -camera (camera) -time (time) -type (type) [others]
+// returns: (type) (class) (exp_flag) DONE
+pmDetrendSelectResults *pmDetrendSelect (const pmDetrendSelectOptions *options)
+{
+    int status, exit_status;
+    char *line = NULL;
+    char *time = psTimeToISO (&options->time);
+    char *type = pmDetrendTypeToString (options->type);
+    unsigned int nFail;
+
+    psArray *array = NULL;
+    psMetadata *answer = NULL;
+
+    pmDetrendSelectResults *results = pmDetrendSelectResultsAlloc();
+
+    // generate the detselect command
+    // psStringAppend (&line, "detselect -inst %s -time %s -det_type %s", options->camera, time, type);
+    // XXX we need to put in the filter and other restrictions based on the recipe
+    // detselect -search -simple returns: DET_ID ITERATION CLASS TYPE
+    psStringAppend (&line, "detselect -search -simple -inst %s -det_type %s", options->camera, type);
+    if (options->filter) {
+        psStringAppend (&line, " -filter %s", options->filter);
+    }
+    if (options->exptime > -1.0) {
+        psStringAppend (&line, " -exptime %f", options->exptime);
+    }
+    if (options->airmass > -1.0) {
+        psStringAppend (&line, " -airmass %f", options->airmass);
+    }
+
+    psTrace("psModules.detrend", 5, "running %s", line);
+
+    // use psPipe to exec the command, wait for response
+    psIOBuffer *buffer = psIOBufferAlloc (512);
+    psPipe *pipe = psPipeOpen (line);
+    if (!pipe) {
+        psError (PS_ERR_IO, false, "error calling command %s", line);
+        goto failure;
+    }
+
+    status = psIOBufferReadEmpty (buffer, 100, pipe->stdout);
+    if (!status) {
+        psError (PS_ERR_IO, false, "detselect is not responding");
+        goto failure;
+    }
+    exit_status = psPipeClose (pipe);
+    if (exit_status) {
+        psError (PS_ERR_IO, false, "error running detselect");
+        goto failure;
+    }
+
+    psTrace("psModules.detrend", 5, "got answer: %s\n", buffer->data);
+
+    array = NULL;
+    answer = NULL;
+    nFail = 0;
+    # define USE_MDPARSE 0
+    # if (USE_MDPARSE)
+
+        answer = psMetadataConfigParse (NULL, &nFail, buffer->data, false);
+    detExp = psMetadataLookupPtr ();
+    char *levelName = psMetadataLookupStr ();
+    # endif
+
+    // XXX need to parse the response more robustly
+    // XXX for now, assume a single line
+    // XXX we should expect a metadata back (if we call without -simple)
+    // XXX we should run psMetatdataParseConfig () on the
+    array = psStringSplitArray (buffer->data, " ", false);
+
+    if (!array) {
+        psError(PS_ERR_IO, false, "failed to split detselect answer %s\n", buffer->data);
+        goto failure;
+    }
+    if (array->n != 4) {
+        psError(PS_ERR_IO, false, "invalid number of fields in detselect answer %s (%ld)\n", buffer->data, array->n);
+        goto failure;
+    }
+
+    pmFPALevel level = pmFPALevelFromName (array->data[2]);
+    if (level == PM_FPA_LEVEL_NONE) {
+        psError(PS_ERR_IO, false, "invalid file level (%s) from detselect\n", (char *)array->data[2]);
+        goto failure;
+    }
+
+    results->level = level;
+    psStringAppend (&results->detID, " -det_id %s -iteration %s ", (char *)array->data[0], (char *)array->data[1]);
+
+    psTrace("psModules.detrend", 5, "generated detID %s\n", results->detID);
+    psTrace("psModules.detrend", 5, "fileLevel is %d (%s)\n", results->level, (char *)array->data[2]);
+    psTrace("psModules.detrend", 5, "selected type is %s\n", (char *)array->data[3]);
+
+    psFree (array);
+    psFree (pipe);
+    psFree (buffer);
+    psFree (line);
+    psFree (type);
+    psFree (time);
+    return results;
+
+failure:
+    psFree (array);
+    psFree (results);
+    psFree (pipe);
+    psFree (buffer);
+    psFree (line);
+    psFree (type);
+    psFree (time);
+    return NULL;
+}
+
+// ************* detrend file functions **************
+
+// detselect -select -detID (detID) -classID (classID)
+// returns: (detID) (classID) (filename) DONE
+char *pmDetrendFile (const char *detID, const char *classID)
+{
+    PS_ASSERT_PTR_NON_NULL(detID, NULL);
+    PS_ASSERT_PTR_NON_NULL(classID, NULL);
+
+    bool status;
+    char *line = NULL;
+    psArray *array = NULL;
+
+    // generate the detselect command
+    psStringAppend (&line, "detselect -select -simple %s -class_id %s", detID, classID);
+    psTrace("psModules.detrend", 5, "running %s", line);
+
+    // use psPipe to exec the command, wait for response
+    psIOBuffer *buffer = psIOBufferAlloc (512);
+    psPipe *pipe = psPipeOpen (line);
+    if (!pipe) {
+        psError (PS_ERR_IO, false, "error calling command %s", line);
+        goto failure;
+    }
+    status = psIOBufferReadEmpty (buffer, 100, pipe->stdout);
+    if (!status) {
+        psError (PS_ERR_IO, false, "detselect is not responding");
+        goto failure;
+    }
+    status = psPipeClose (pipe);
+    if (status) {
+        psError (PS_ERR_IO, false, "error running detselect");
+        goto failure;
+    }
+
+    psTrace("psModules.detrend", 5, "got answer: %s\n", buffer->data);
+
+    // XXX need to parse the response more robustly
+    // XXX for now, assume a single line
+    array = psStringSplitArray (buffer->data, " ", false);
+
+    if (!array) {
+        psError(PS_ERR_IO, false, "failed to split detselect answer %s\n", buffer->data);
+        goto failure;
+    }
+    if (array->n == 0) {
+        psError(PS_ERR_IO, true, "empty result set from detselect\n");
+        goto failure;
+    }
+    if (array->n < 5) {
+        psError(PS_ERR_IO, true, "invalid result set from detselect %s\n", buffer->data);
+        goto failure;
+    }
+
+    char *result = psStringCopy (array->data[3]);
+    psTrace("psModules.detrend", 5, "detrend file: %s\n", result);
+
+    psFree (array);
+    psFree (pipe);
+    psFree (buffer);
+    psFree (line);
+    return result;
+
+failure:
+    psFree (array);
+    psFree (pipe);
+    psFree (buffer);
+    psFree (line);
+    return NULL;
+}
Index: /tags/RHL_v1_1/psModules/src/detrend/pmDetrendDB.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmDetrendDB.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmDetrendDB.h	(revision 9533)
@@ -0,0 +1,68 @@
+/** @file  pmDetrendDB.h
+*
+*  @brief Tools to query the detrend database system
+*
+*  the functions in here do not perform the detrend database queries directly.  all interfaces
+*  to the detrend database go through the external dettools functions.  this allows the modules
+*  and directly dependent program to be sufficiently independent of the database schema that it
+*  can be used with any properly defined detrend database tables.  
+*
+*  XXX place the specific name of the detrend database query command in the configuration data
+*
+*  @ingroup Detrend
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-10-10 01:02:25 $
+*
+*  Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+*/
+
+#ifndef PM_DETREND_DB_H
+#define PM_DETREND_DB_H
+
+typedef enum {
+    PM_DETREND_TYPE_MASK,
+    PM_DETREND_TYPE_BIAS,
+    PM_DETREND_TYPE_DARK,
+    PM_DETREND_TYPE_FLAT,
+    PM_DETREND_TYPE_FLAT_CORRECTION,
+    PM_DETREND_TYPE_FRINGE_IMAGE,
+    PM_DETREND_TYPE_FRINGE_TABLE,
+    PM_DETREND_TYPE_BACKGROUND,
+} pmDetrendType;
+
+typedef struct
+{
+    char *camera;   // name of camera
+    psTime time;   // time of input data
+    pmDetrendType type;   // type of detrend data
+    char *filter;   // name of filter
+    float exptime;   // exposure time (for dark, maybe flat & fringe)
+    float airmass;   // for fringe
+}
+pmDetrendSelectOptions;
+
+typedef struct
+{
+    char *detID;   // identifier of detrend run
+    pmFPALevel level;   // level in FPA hierarchy of individual file
+}
+pmDetrendSelectResults;
+
+typedef struct
+{
+    char *detID;   // identifier of detrend image
+    char *classID;   // level in FPA hierarchy of individual file
+}
+pmDetrendFileOptions;
+
+psString pmDetrendTypeToString (pmDetrendType type);
+
+pmDetrendSelectOptions *pmDetrendSelectOptionsAlloc(const char *camera, psTime time, pmDetrendType type);
+pmDetrendSelectResults *pmDetrendSelectResultsAlloc();
+pmDetrendSelectResults *pmDetrendSelect (const pmDetrendSelectOptions *options);
+char *pmDetrendFile (const char *detID, const char *classID);
+
+# endif
Index: /tags/RHL_v1_1/psModules/src/detrend/pmFlatField.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmFlatField.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmFlatField.c	(revision 9533)
@@ -0,0 +1,119 @@
+/** @file  pmFlatField.c
+ *
+ *  @brief Given an input image and a flat field image, pmFlatField shall divide the input image by the flat
+ *  field image.
+ *
+ *  The input image, in, and the flat field image, flat, need not be the same size, since the input image may
+ *  already have been trimmed (following overscan subtraction), but the function shall use the offsets in the
+ *  image (in->x0 and in->y0) to determine the appropriate offsets to obtain the correct pixel on the flat
+ *  field. In the event that the flat image is too small (i.e., pixels on the input image refer to pixels
+ *  outside the range of the flat image), the function shall generate an error. Pixels which are negative or
+ *  zero in the flat shall be masked in the input image with the value PM_MASK_FLAT. Negative pixels in the
+ *  flat may be set to zero so that they are treated identically to zeroes. Any pixels masked in the flat
+ *  shall be masked with corresponding values in the output. The function shall not normalize the flat; this
+ *  responsibility is left to the caller. This function is basically equivalent to a divide (with psImageOp),
+ *  but with care for the region that is divided, checking for negative pixels, and copying of the mask from
+ *  the flat to the output.
+ *
+ *  @author Ross Harman, MHPCC
+ *
+ *  @version $Revision: 1.13 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-07-10 20:54:52 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <strings.h>
+
+#include <pslib.h>
+#include "pmFPA.h"
+#include "pmFlatField.h"
+#include "pmFPAMaskWeight.h"
+#include "pmFlatFieldErrors.h"
+
+
+bool pmFlatField(pmReadout *in, const pmReadout *flat)
+{
+    PS_ASSERT_PTR_NON_NULL(in, false);
+    PS_ASSERT_PTR_NON_NULL(in->image, false);
+    PS_ASSERT_IMAGE_NON_EMPTY(in->image, false);
+    PS_ASSERT_PTR_NON_NULL(flat, false);
+    PS_ASSERT_PTR_NON_NULL(flat->image, false);
+    PS_ASSERT_IMAGE_NON_EMPTY(flat->image, false);
+    if (in->mask) {
+        PS_ASSERT_IMAGE_TYPE(in->mask, PS_TYPE_MASK, false);
+        PS_ASSERT_IMAGES_SIZE_EQUAL(in->mask, in->image, false);
+    }
+    PS_ASSERT_IMAGE_TYPE(flat->image, in->image->type.type, false);
+
+    psImage *inImage   = in->image;     // Input image
+    psImage *inMask    = in->mask;      // Mask for input image
+    psImage *flatImage = flat->image;   // Flat-field image
+
+    // Check input image is not larger than flat image; mask is the same size as the input
+    if (inImage->numRows > flatImage->numRows || inImage->numCols > flatImage->numCols) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Input image (%dx%d) is larger than flat-field image "
+                "(%dx%d).\n", inImage->numCols, inImage->numRows, flatImage->numCols, flatImage->numRows);
+        return false;
+    }
+
+    // Offsets on the chip
+    int x0in = psMetadataLookupS32(NULL, in->parent->concepts, "CELL.X0");
+    int y0in = psMetadataLookupS32(NULL, in->parent->concepts, "CELL.Y0");
+    int x0flat = psMetadataLookupS32(NULL, flat->parent->concepts, "CELL.X0");
+    int y0flat = psMetadataLookupS32(NULL, flat->parent->concepts, "CELL.Y0");
+
+    // Determine offset based on image offset with chip offset: input frame to flat frame
+    int yOffset = in->row0 + y0in - flat->col0 - y0flat;
+    int xOffset = in->row0 + x0in - flat->col0 - x0flat;
+
+    // Check that offsets are within image limits
+    if (inImage->numRows + yOffset > flatImage->numRows ||
+            inImage->numCols + xOffset > flatImage->numCols) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true, "Input image (%dx%d) with offsets (%d,%d) is larger than "
+                "flat-field image (%dx%d).\n", inImage->numCols, inImage->numRows, xOffset, yOffset,
+                flatImage->numCols, flatImage->numRows);
+        return false;
+    }
+
+    // Macro for all PS types
+    #define FLAT_DIVISION_CASE(TYPE, SPECIAL) \
+case PS_TYPE_##TYPE: \
+    for (int j = 0; j < inImage->numRows; j++) { \
+        for (int i = 0; i < inImage->numCols; i++) { \
+            if (flatImage->data.TYPE[j][i] <= 0.0) { \
+                if (inMask) { \
+                    inMask->data.U8[j][i] |= PM_MASK_FLAT; \
+                } \
+                inImage->data.TYPE[j][i] = SPECIAL; \
+            } else { \
+                inImage->data.TYPE[j][i] /= flatImage->data.TYPE[j][i]; \
+            } \
+        } \
+    } \
+    break;
+
+    switch (inImage->type.type) {
+        FLAT_DIVISION_CASE(U8,  0);
+        FLAT_DIVISION_CASE(U16, 0);
+        FLAT_DIVISION_CASE(U32, 0);
+        FLAT_DIVISION_CASE(U64, 0);
+        FLAT_DIVISION_CASE(S8,  0);
+        FLAT_DIVISION_CASE(S16, 0);
+        FLAT_DIVISION_CASE(S32, 0);
+        FLAT_DIVISION_CASE(S64, 0);
+        FLAT_DIVISION_CASE(F32, NAN);
+        FLAT_DIVISION_CASE(F64, NAN);
+    default:
+        psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Unsupported type for input image: %x\n", inImage->type.type);
+        return false;
+    }
+
+    return true;
+}
Index: /tags/RHL_v1_1/psModules/src/detrend/pmFlatField.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmFlatField.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmFlatField.h	(revision 9533)
@@ -0,0 +1,41 @@
+/** @file  pmFlatField.h
+ *
+ *  @brief Given an input image and a flat field image, pmFlatField shall divide the input image by the flat
+ *  field image.
+ *
+ *  The input image, in, and the flat field image, flat, need not be the same size, since the input image may
+ *  already have been trimmed (following overscan subtraction), but the function shall use the offsets in the
+ *  image (in->x0 and in->y0) to determine the appropriate offsets to obtain the correct pixel on the flat
+ *  field. In the event that the flat image is too small (i.e., pixels on the input image refer to pixels
+ *  outside the range of the flat image), the function shall generate an error. Pixels which are negative or
+ *  zero in the flat shall be masked in the input image with the value PM_MASK_FLAT. Negative pixels in the
+ *  flat may be set to zero so that they are treated identically to zeroes. Any pixels masked in the flat
+ *  shall be masked with corresponding values in the output. The function shall not normalize the flat; this
+ *  responsibility is left to the caller. This function is basically equivalent to a divide (with psImageOp),
+ *  but with care for the region that is divided, checking for negative pixels, and copying of the mask from
+ *  the flat to the output.
+ *
+ *  @author Ross Harman, MHPCC
+ *
+ *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-06-02 00:55:23 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+
+/** Execute flat field module.
+ *
+ *  Given an input image and a flat-field image, pmFlatField shall divide the input image by the flat field
+ *  image.
+ *
+ *  @return  bool: True or false for success or failure
+ */
+bool pmFlatField(
+    pmReadout *in,          ///< Readout with input image
+    const pmReadout *flat   ///< Readout with flat image
+);
+
Index: /tags/RHL_v1_1/psModules/src/detrend/pmFlatFieldErrors.dat
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmFlatFieldErrors.dat	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmFlatFieldErrors.dat	(revision 9533)
@@ -0,0 +1,22 @@
+#
+#  This file is used to generate pmFlatFieldErrors.h content
+#
+#  Format is:
+#  ERRORNAME(one word)    ERRORTEXT
+#
+#  N.B. in code, the ERRORNAME appears as PS_ERRORTEXT_ERRORNAME
+####################################################################
+#
+pmFlatField_NULL_FLAT_READOUT          Null not allowed for flat readout.
+pmFlatField_NULL_INPUT_IMAGE           Null not allowed for input image.
+pmFlatField_NULL_FLAT_IMAGE            Null not allowed for flat image.
+pmFlatField_SIZE_INPUT_IMAGE           Input image size exceeds that of flat image: (%d, %d) vs (%d, %d)
+pmFlatField_SIZE_MASK_IMAGE            Input image mask size exceeds that of flat image: (%d, %d) vs (%d, %d)
+pmFlatField_OFFSET_FLAT_IMAGE          Total offset >= flat image size: (%d, %d) vs (%d, %d)
+pmFlatField_OFFSET_INPUT_IMAGE         Total offset >= input image: (%d, %d) vs (%d, %d)
+pmFlatField_OFFSET_MASK_IMAGE          Total offset >= input image mask: (%d, %d) vs (%d, %d)
+pmFlatField_TYPE_INPUT_IMAGE           Complex types not allowed for input image. Type: %d
+pmFlatField_TYPE_FLAT_IMAGE            Complex types not allowed for flat image. Type: %d
+pmFlatField_TYPE_MASK_IMAGE            Mask must be PS_TYPE_MASK type. Type: %d
+pmFlatField_TYPE_MISMATCH              Input and flat image types differ: (%d vs %d)
+pmFlatField_TYPE_UNSUPPORTED           Unsupported image datatype. Type: %d
Index: /tags/RHL_v1_1/psModules/src/detrend/pmFlatFieldErrors.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmFlatFieldErrors.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmFlatFieldErrors.h	(revision 9533)
@@ -0,0 +1,47 @@
+/** @file  pmFlatFieldErrors.h
+ *
+ *  @brief Contains the error text for the flat field module
+ *
+ *  @ingroup ErrorHandling
+ *
+ *  @author Ross Harman, MHPCC
+ *
+ *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2005-09-28 20:43:52 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#ifndef PM_FLATFIELD_ERRORS_H
+#define PM_FLATFIELD_ERRORS_H
+
+/* N.B., lines between '//~Start' and '//~End' are automatic generated from
+ * the template following the '//~Start'.  The template is used to generate
+ * the other lines by, for each error text in psDataManipErrors.dat, the following
+ * substitutions are made:
+ *     $1  The error text macro name (first word in the psFlatFieldErrors.h lines)
+ *     $2  The error text (rest of the line in psFlatFieldErrors.h)
+ *     $n  The order of the source line in psFlatFieldErrors.h (comments excluded)
+ *
+ * DO NOT EDIT THE LINES BETWEEN //~Start and //~End!  ANY CHANGES WILL BE OVERWRITTEN.
+ */
+
+#define PS_ERRORNAME_DOMAIN "psModule.src."
+
+//~Start #define PS_ERRORTEXT_$1 "$2"
+#define PS_ERRORTEXT_pmFlatField_NULL_FLAT_READOUT "Null not allowed for flat readout."
+#define PS_ERRORTEXT_pmFlatField_NULL_INPUT_IMAGE "Null not allowed for input image."
+#define PS_ERRORTEXT_pmFlatField_NULL_FLAT_IMAGE "Null not allowed for flat image."
+#define PS_ERRORTEXT_pmFlatField_SIZE_INPUT_IMAGE "Input image size exceeds that of flat image: (%d, %d) vs (%d, %d)"
+#define PS_ERRORTEXT_pmFlatField_SIZE_MASK_IMAGE "Input image mask size exceeds that of flat image: (%d, %d) vs (%d, %d)"
+#define PS_ERRORTEXT_pmFlatField_OFFSET_FLAT_IMAGE "Total offset >= flat image size: (%d, %d) vs (%d, %d)"
+#define PS_ERRORTEXT_pmFlatField_OFFSET_INPUT_IMAGE "Total offset >= input image: (%d, %d) vs (%d, %d)"
+#define PS_ERRORTEXT_pmFlatField_OFFSET_MASK_IMAGE "Total offset >= input image mask: (%d, %d) vs (%d, %d)"
+#define PS_ERRORTEXT_pmFlatField_TYPE_INPUT_IMAGE "Complex types not allowed for input image. Type: %d"
+#define PS_ERRORTEXT_pmFlatField_TYPE_FLAT_IMAGE "Complex types not allowed for flat image. Type: %d"
+#define PS_ERRORTEXT_pmFlatField_TYPE_MASK_IMAGE "Mask must be PS_TYPE_MASK type. Type: %d"
+#define PS_ERRORTEXT_pmFlatField_TYPE_MISMATCH "Input and flat image types differ: (%d vs %d)"
+#define PS_ERRORTEXT_pmFlatField_TYPE_UNSUPPORTED "Unsupported image datatype. Type: %d"
+//~End
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/detrend/pmFlatNormalize.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmFlatNormalize.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmFlatNormalize.c	(revision 9533)
@@ -0,0 +1,198 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <math.h>
+#include <pslib.h>
+
+#include "pmFlatNormalize.h"
+
+// I'm not sure that many many iterations are required, but rather suspect that the system converges within a
+// few with absolutely no trouble (it *is* over-constrained).  For this reason, I'm putting the maximum number
+// of iterations and tolerance as preset values.
+#define MAXITER 10
+#define TOLERANCE 1e-3
+
+// Estimate the flat-field normalisation; return the source flux in each integration
+bool pmFlatNormalize(psVector **expFluxesPtr, // Flux in each exposure; modified for return
+                     psVector **chipGainsPtr, // Initial guess of the chip gains; modified for return
+                     const psImage *bgMatrix
+                    )
+{
+    PS_ASSERT_PTR_NON_NULL(bgMatrix, false);
+    PS_ASSERT_IMAGE_NON_NULL(bgMatrix, false);
+
+    int numExps = bgMatrix->numRows; // Number of exposures
+    int numChips = bgMatrix->numCols; // Number of chips with which each exposure is made
+
+    psVector *expFluxes;                // Dereferenced version of expFluxesPtr
+    if (expFluxesPtr) {
+        if (*expFluxesPtr) {
+            PS_ASSERT_VECTOR_TYPE(*expFluxesPtr, PS_TYPE_F32, false);
+            PS_ASSERT_VECTOR_SIZE(*expFluxesPtr, (long)numExps, false);
+        } else {
+            *expFluxesPtr = psVectorAlloc(numExps, PS_TYPE_F32);
+            (*expFluxesPtr)->n = numExps;
+        }
+        expFluxes = psMemIncrRefCounter(*expFluxesPtr);
+    } else {
+        expFluxes = psVectorAlloc(numExps, PS_TYPE_F32);
+        expFluxes->n = numExps;
+    }
+
+    psVector *chipGains;                // Dereferenced version of chipGainsPtr
+    if (chipGainsPtr) {
+        if (*chipGainsPtr) {
+            PS_ASSERT_VECTOR_TYPE(*chipGainsPtr, PS_TYPE_F32, false);
+            PS_ASSERT_VECTOR_SIZE(*chipGainsPtr, (long)numChips, false);
+        } else {
+            *chipGainsPtr = psVectorAlloc(numChips, PS_TYPE_F32);
+            (*chipGainsPtr)->n = numChips;
+            psVectorInit(*chipGainsPtr, 1.0);
+        }
+        chipGains = psMemIncrRefCounter(*chipGainsPtr);
+    } else {
+        chipGains = psVectorAlloc(numChips, PS_TYPE_F32);
+        chipGains->n = numChips;
+        psVectorInit(chipGains, 1.0);
+    }
+
+    // Take the logarithms
+    psImage *flux = psImageCopy(NULL, bgMatrix, PS_TYPE_F32); // Copy of the input flux levels matrix
+    psImage *fluxMask = psImageAlloc(numChips, numExps, PS_TYPE_U8); // Mask for bad measurements
+    psImageInit(fluxMask, 0);
+    psVector *gainMask = psVectorAlloc(numChips, PS_TYPE_U8); // Mask for bad gains
+    gainMask->n = numChips;
+    psVectorInit(gainMask, 0);
+    psVector *expMask = psVectorAlloc(numExps, PS_TYPE_U8); // Mask for bad exposures
+    expMask->n = numExps;
+    psVectorInit(expMask, 0);
+    for (int i = 0; i < numChips; i++) {
+        // Note: the input gains are in e/ADU; we want to work with ADU/e (bg [ADU] = g [ADU/e] * f [e])
+        // Hence the minus sign
+        if (isfinite(chipGains->data.F32[i]) && chipGains->data.F32[i] > 0) {
+            chipGains->data.F32[i] = -logf(chipGains->data.F32[i]);
+        } else {
+            chipGains->data.F32[i] = 0.0; // Take a wild guess, gain ~ 1 e/ADU
+        }
+
+        for (int j = 0; j < numExps; j++) {
+            if (isfinite(flux->data.F32[j][i]) && flux->data.F32[j][i] > 0) {
+                flux->data.F32[j][i] = logf(flux->data.F32[j][i]);
+            } else {
+                // Blank out this measurement
+                fluxMask->data.U8[j][i] = 1;
+                flux->data.F32[j][i] = NAN;
+            }
+        }
+    }
+
+    // Not really sure that we need to iterate, but here we go anyway...
+
+    float diff = INFINITY;              // Difference from previous iteration
+    psVector *oldExpFluxes = NULL;      // The fluxes in the previous iteration
+    psVector *oldChipGains = NULL;      // Chip gains in the previous iteration
+    for (int iter = 0; iter < MAXITER && diff > TOLERANCE; iter++) {
+        // Improve on the exposure fluxes
+        int numFluxes = 0;              // Number of fluxes
+        for (int i = 0; i < numExps; i++) {
+            if (expMask->data.U8[i]) {
+                psTrace("psModules.detrend", 7, "Flux for exposure %d is masked.\n", i);
+                continue;
+            }
+            numFluxes++;
+            float sum = 0.0;            // Sum of F_ij - G_j
+            int number = 0;             // Number of chips contributing
+            for (int j = 0; j < numChips; j++) {
+                if (!gainMask->data.U8[j] && !fluxMask->data.U8[i][j]) {
+                    sum += flux->data.F32[i][j] - chipGains->data.F32[j];
+                    number++;
+                }
+            }
+            if (number > 0) {
+                expFluxes->data.F32[i] = sum / (float)number;
+            } else {
+                expMask->data.U8[i] = 1;
+                expFluxes->data.F32[i] = NAN;
+            }
+            psTrace("psModules.detrend", 7, "Flux for exposure %d is %lf\n", i, expf(expFluxes->data.F32[i]));
+        }
+
+        // Improve on the gains
+        float meanGain = 0.0;           // Mean gain
+        int numGains = 0;               // Number of gains
+        for (int i = 0; i < numChips; i++) {
+            if (gainMask->data.U8[i]) {
+                continue;
+            }
+            float sum = 0.0;           // Sum of F_ji - S_j
+            int number = 0;             // Numer of sources contributing
+            for (int j = 0; j < numExps; j++) {
+                if (!fluxMask->data.U8[j][i]) {
+                    sum += flux->data.F32[j][i] - expFluxes->data.F32[j];
+                    number++;
+                }
+            }
+            if (number > 0) {
+                chipGains->data.F32[i] = sum / (float)number;
+            } else {
+                gainMask->data.U8[i] = 1;
+                chipGains->data.F32[i] = NAN;
+            }
+            psTrace("psModules.detrend", 7, "Gain for chip %d is %lf\n", i, expf(-chipGains->data.F32[i]));
+            meanGain += expf(chipGains->data.F32[i]);
+            numGains++;
+        }
+
+        // Normalise the mean gain to unity, and measure the difference
+        meanGain /= (float)numGains;
+        meanGain = logf(meanGain);
+        if (iter > 0) {
+            diff = 0.0;
+            for (int i = 0; i < numChips; i++) {
+                if (gainMask->data.U8[i]) {
+                    continue;
+                }
+                chipGains->data.F32[i] -= meanGain;
+                diff += abs((chipGains->data.F32[i] - oldChipGains->data.F32[i]) / chipGains->data.F32[i]);
+            }
+            for (int i = 0; i < numExps; i++) {
+                if (expMask->data.U8[i]) {
+                    continue;
+                }
+                diff += abs((expFluxes->data.F32[i] - oldExpFluxes->data.F32[i]) / expFluxes->data.F32[i]);
+            }
+        }
+
+        psTrace("psModules.detrend", 2, "Iteration %d: difference is %e\n", iter, diff);
+
+        // Copy the new to the old
+        oldChipGains = psVectorCopy(oldChipGains, chipGains, PS_TYPE_F32);
+        oldExpFluxes = psVectorCopy(oldExpFluxes, expFluxes, PS_TYPE_F32);
+    }
+    psFree(flux);
+    psFree(fluxMask);
+    psFree(oldChipGains);
+    psFree(oldExpFluxes);
+
+    // Un-log the vectors
+    for (int i = 0; i < numChips; i++) {
+        if (!gainMask->data.U8[i]) {
+            chipGains->data.F32[i] = expf(chipGains->data.F32[i]);
+        }
+    }
+    for (int i = 0; i < numExps; i++) {
+        if (!expMask->data.U8[i]) {
+            expFluxes->data.F32[i] = expf(expFluxes->data.F32[i]);
+        }
+    }
+    psFree(gainMask);
+    psFree(expMask);
+
+    psFree(chipGains);
+    psFree(expFluxes);
+
+    return (diff < TOLERANCE); // Did we converge?
+}
Index: /tags/RHL_v1_1/psModules/src/detrend/pmFlatNormalize.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmFlatNormalize.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmFlatNormalize.h	(revision 9533)
@@ -0,0 +1,13 @@
+#ifndef PM_FLAT_NORMALIZE_H
+#define PM_FLAT_NORMALIZE_H
+
+#include "pslib.h"
+
+// Normalise the flat-field measurements (f_ij = g_i s_j where f_ij is the flux recorded for chip i and
+// integration j, g_i is the gain for the i-th chip, s_j is the flux of the source in the j-th integration).
+bool pmFlatNormalize(psVector **expFluxesPtr, // Flux in each exposure; modified for return
+                     psVector **chipGainsPtr, // Initial guess of the chip gains; modified for return
+                     const psImage *bgMatrix
+                    );
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/detrend/pmFringeStats.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmFringeStats.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmFringeStats.c	(revision 9533)
@@ -0,0 +1,844 @@
+/** @file  pmFringeStats.c
+ *
+ *  @author Eugene Magnier, IfA
+ *
+ *  @version $Revision: 1.19 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-20 02:36:36 $
+ *
+ *  Copyright 2004 IfA
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmFPA.h"
+#include "pmFringeStats.h"
+
+
+// Future optimisations for speed:
+//
+// 1. Clipping --- don't re-do the matrix setup again, but carry matrix and vector around, subtract
+// contributions from clipped data points.
+// 2. Faster psImageStats (use memcpy?)
+
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// pmFringeRegions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static void fringeRegionsFree(pmFringeRegions *fringe)
+{
+    psFree(fringe->x);
+    psFree(fringe->y);
+    psFree(fringe->mask);
+    return;
+}
+
+pmFringeRegions *pmFringeRegionsAlloc(int nPts, int dX, int dY, int nX, int nY)
+{
+    pmFringeRegions *fringe = psAlloc(sizeof(pmFringeRegions));
+    (void)psMemSetDeallocator(fringe, (psFreeFunc)fringeRegionsFree);
+
+    fringe->x = NULL;
+    fringe->y = NULL;
+    fringe->mask = NULL;
+
+    fringe->nRequested = nPts;
+    fringe->nAccepted = 0;
+
+    fringe->dX = dX;
+    fringe->dY = dY;
+    fringe->nX = nX;
+    fringe->nY = nY;
+
+    return fringe;
+}
+
+bool pmFringeRegionsCreatePoints(pmFringeRegions *fringe, const psImage *image, psRandom *random)
+{
+    PS_ASSERT_PTR_NON_NULL(fringe, false);
+    PS_ASSERT_PTR_NON_NULL(image, false);
+    PS_ASSERT_IMAGE_NON_EMPTY(image, false);
+
+    double frnd;
+    // create fringe->nRequested
+
+    psRandom *rng;
+    if (random) {
+        rng = psMemIncrRefCounter(random);
+    } else {
+        rng = psRandomAlloc(PS_RANDOM_TAUS, 0);
+    }
+
+    fringe->x = psVectorRecycle(fringe->x, fringe->nRequested, PS_TYPE_F32);
+    fringe->y = psVectorRecycle(fringe->y, fringe->nRequested, PS_TYPE_F32);
+    fringe->mask = psVectorRecycle(fringe->mask, fringe->nRequested, PS_TYPE_U8);
+    fringe->x->n = fringe->y->n = fringe->mask->n = fringe->nRequested;
+    psVectorInit(fringe->mask, 0);
+
+    int nX = image->numCols;
+    int nY = image->numRows;
+
+    psF32 *xPt = fringe->x->data.F32;
+    psF32 *yPt = fringe->y->data.F32;
+
+    int dX = fringe->dX;
+    int dY = fringe->dY;
+
+    // generate random points located within image bounds
+    for (int i = 0; i < fringe->nRequested; i++) {
+        frnd = psRandomUniform(rng);
+        xPt[i] = (nX - 2*dX)* frnd + dX;
+        frnd = psRandomUniform(rng);
+        yPt[i] = (nY - 2*dY)* frnd + dY;
+    }
+
+    psFree(rng);
+
+    return true;
+}
+
+bool pmFringeRegionsWriteFits(psFits *fits, psMetadata *header,
+                              const pmFringeRegions *regions, const char *extname)
+{
+    // Make sure the input is well-behaved
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_PTR_NON_NULL(regions, false);
+    psVector *x = regions->x;           // The x positions
+    psVector *y = regions->y;           // The y positions
+    psVector *mask = regions->mask;     // The region mask
+    int numRows = regions->nRequested;  // Number of rows in the table
+    PS_ASSERT_INT_POSITIVE(numRows, false);
+    PS_ASSERT_VECTOR_NON_NULL(x, false);
+    PS_ASSERT_VECTOR_NON_NULL(y, false);
+    PS_ASSERT_VECTOR_TYPE(x, PS_TYPE_F32, false);
+    PS_ASSERT_VECTOR_TYPE(y, PS_TYPE_F32, false);
+    PS_ASSERT_VECTOR_SIZE(x, (long)numRows, false);
+    PS_ASSERT_VECTOR_SIZE(y, (long)numRows, false);
+    if (mask) {
+        PS_ASSERT_VECTOR_NON_NULL(mask, false);
+        PS_ASSERT_VECTOR_TYPE(mask, PS_TYPE_U8, false);
+        PS_ASSERT_VECTOR_SIZE(mask, (long)numRows, false);
+    }
+
+    // We need to write:
+    // Scalars: dX, dY, nX, nY
+    // Vectors: x, y
+
+    psMetadata *scalars = psMetadataAlloc(); // Metadata to hold the scalars; will be the header
+    psMetadataAddS32(scalars, PS_LIST_TAIL, "PSFRNGDX", PS_META_REPLACE, "Median box half-width",
+                     regions->dX);
+    psMetadataAddS32(scalars, PS_LIST_TAIL, "PSFRNGDY", PS_META_REPLACE, "Median box half-height",
+                     regions->dY);
+    psMetadataAddS32(scalars, PS_LIST_TAIL, "PSFRNGNX", PS_META_REPLACE, "Large-scale smoothing in x",
+                     regions->nX);
+    psMetadataAddS32(scalars, PS_LIST_TAIL, "PSFRNGNY", PS_META_REPLACE, "Large-scale smoothing in y",
+                     regions->nY);
+
+    psArray *table = psArrayAlloc(numRows); // The table
+    table->n = numRows;
+    // Translate the vectors into the required format for psFitsWriteTable()
+    for (long i = 0; i < numRows; i++) {
+        psMetadata *row = psMetadataAlloc();
+        psMetadataAddF32(row, PS_LIST_TAIL, "x", PS_META_REPLACE, "Fringe position in x", x->data.F32[i]);
+        psMetadataAddF32(row, PS_LIST_TAIL, "y", PS_META_REPLACE, "Fringe position in y", y->data.F32[i]);
+        table->data[i] = row;
+    }
+
+    bool success;                       // Success of operation
+    if (!(success = psFitsWriteTable(fits, scalars, table, extname))) {
+        psError(PS_ERR_IO, false, "Unable to write fringe data to extension %s\n", extname);
+    }
+    psFree(scalars);
+    psFree(table);
+
+    return success;
+}
+
+pmFringeRegions *pmFringeRegionsReadFits(psMetadata *header, const psFits *fits, const char *extname)
+{
+    PS_ASSERT_PTR_NON_NULL(fits, NULL);
+
+    if (extname && strlen(extname) > 0) {
+        if (!psFitsMoveExtName(fits, extname)) {
+            psError(PS_ERR_IO, false, "Unable to move to extension %s\n", extname);
+            return NULL;
+        }
+    } else if (!psFitsMoveExtNum(fits, 0, false)) {
+        psError(PS_ERR_IO, false, "Unable to move to PHU\n");
+        return NULL;
+    }
+
+    psMetadata *headerCopy = psMemIncrRefCounter(header); // Copy of the header, or NULL
+
+    headerCopy = psFitsReadHeader(headerCopy, fits); // The FITS header
+    if (!headerCopy) {
+        psError(PS_ERR_IO, false, "Unable to read header for extension %s\n", extname);
+        psFree(header);
+        return NULL;
+    }
+
+    // Read the scalars from the header
+    #define READ_SCALAR(SCALAR, NAME) \
+    int SCALAR = psMetadataLookupS32(&mdok, headerCopy, NAME); \
+    if (!mdok || SCALAR <= 0) { \
+        psError(PS_ERR_IO, true, "Unable to find " NAME " in header of extension %s.\n", extname); \
+        psFree(headerCopy); \
+        return NULL; \
+    }
+
+    // Need to retrieve the scalars: dX, dY, nX, nY
+    bool mdok = true;                   // Status of MD lookup
+    READ_SCALAR(dX, "PSFRNGDX");
+    READ_SCALAR(dY, "PSFRNGDY");
+    READ_SCALAR(nX, "PSFRNGNX");
+    READ_SCALAR(nY, "PSFRNGNY");
+    psFree(headerCopy);
+
+    // Now the vectors: x, y
+    psArray *table = psFitsReadTable(fits); // The table
+    long numRows = table->n;            // Number of rows
+
+    pmFringeRegions *regions = pmFringeRegionsAlloc(numRows, dX, dY, nX, nY); // The fringe regions
+    psVector *x = psVectorAlloc(numRows, PS_TYPE_F32); // x position
+    psVector *y = psVectorAlloc(numRows, PS_TYPE_F32); // y position
+    x->n = y->n = numRows;
+    regions->x = x;
+    regions->y = y;
+
+    #define READ_REGIONS_ROW(VECTOR, TYPE, NAME, DESCRIPTION) \
+    VECTOR->data.TYPE[i] = psMetadataLookup##TYPE(&mdok, row, NAME); \
+    if (!mdok) { \
+        psError(PS_ERR_IO, true, "Unable to find " #DESCRIPTION " .\n"); \
+        psFree(table); \
+        psFree(regions); \
+        return NULL; \
+    }
+
+    // Translate the table into vectors
+    for (long i = 0; i < numRows; i++) {
+        psMetadata *row = table->data[i]; // Table row
+        READ_REGIONS_ROW(x, F32, "x", "x position");
+        READ_REGIONS_ROW(y, F32, "y", "y position");
+    }
+    psFree(table);
+
+    return regions;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// pmFringeStats
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static void fringeStatsFree(pmFringeStats *stats)
+{
+    psFree(stats->regions);
+    psFree(stats->f);
+    psFree(stats->df);
+}
+
+pmFringeStats *pmFringeStatsAlloc(pmFringeRegions *regions)
+{
+    PS_ASSERT_PTR_NON_NULL(regions, false);
+
+    pmFringeStats *stats = psAlloc(sizeof(pmFringeStats));
+    (void)psMemSetDeallocator(stats, (psFreeFunc)fringeStatsFree);
+
+    int numRegions = regions->x->n;     // Number of regions
+    stats->regions = psMemIncrRefCounter(regions);
+    stats->f = psVectorAlloc(numRegions, PS_TYPE_F32);
+    stats->df = psVectorAlloc(numRegions, PS_TYPE_F32);
+    stats->f->n = stats->df->n = numRegions;
+
+    return stats;
+}
+
+pmFringeStats *pmFringeStatsMeasure(pmFringeRegions *fringe, pmReadout *readout, psMaskType maskVal)
+{
+    PS_ASSERT_PTR_NON_NULL(fringe, NULL);
+    PS_ASSERT_PTR_NON_NULL(readout, NULL);
+    PS_ASSERT_PTR_NON_NULL(readout->image, NULL);
+    PS_ASSERT_IMAGE_NON_EMPTY(readout->image, NULL);
+
+    if (!fringe->x || !fringe->y) {
+        // create the fringe vectors for this image
+        pmFringeRegionsCreatePoints(fringe, readout->image, NULL);
+    }
+
+    PS_ASSERT_PTR_NON_NULL(fringe->x, false);
+    PS_ASSERT_PTR_NON_NULL(fringe->y, false);
+
+    pmFringeStats *measurements = pmFringeStatsAlloc(fringe);
+
+    psF32 *xPt = fringe->x->data.F32;
+    psF32 *yPt = fringe->y->data.F32;
+    psF32 *fPt = measurements->f->data.F32;
+    psF32 *dfPt = measurements->df->data.F32;
+
+    int dX = fringe->dX;
+    int dY = fringe->dY;
+
+    psImage *image = readout->image;
+    psImage *mask  = readout->mask;
+
+    psStats *median = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN); // Median statistics only
+    psStats *medianSd = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV); // Median and SD
+
+    // Measure the sky in each smoothing box
+    psImage *sky = psImageAlloc(fringe->nX, fringe->nY, PS_TYPE_F32);
+    for (int i = 0; i < fringe->nY; i++) {
+        int y0 = image->row0 + (float)i * (float)image->numRows / (float)fringe->nY;
+        int y1 = image->row0 + (float)(i + 1) * (float)image->numRows / (float)fringe->nY;
+        for (int j = 0; j < fringe->nX; j++) {
+            int x0 = image->col0 + (float)j * (float)image->numCols / (float)fringe->nX;
+            int x1 = image->col0 + (float)(j + 1) * (float)image->numCols / (float)fringe->nX;
+            psRegion region = psRegionSet(x0, x1, y0, y1);
+            psImage *subImage = psImageSubset(image, region); // Subimage of the sky region
+            psImage *subMask = NULL;
+            if (mask) {
+                subMask = psImageSubset(mask, region); // Subimage of the sky region
+            }
+            psImageStats(median, subImage, subMask, maskVal);
+            sky->data.F32[i][j] = median->sampleMedian;
+            psFree(subImage);
+            psFree(subMask);
+        }
+    }
+
+    for (int i = 0; i < fringe->x->n; i++) {
+        psRegion region = psRegionSet(image->col0 + xPt[i] - dX,
+                                      image->col0 + xPt[i] + dX + 1,
+                                      image->row0 + yPt[i] - dY,
+                                      image->row0 + yPt[i] + dY + 1);
+        psImage *subImage = psImageSubset(image, region);
+        psImage *subMask = NULL;
+        if (mask) {
+            subMask = psImageSubset(mask, region);
+        }
+        psImageStats(medianSd, subImage, subMask, maskVal);
+        psFree(subImage);
+        psFree(subMask);
+
+        int xSky = xPt[i] / (float)image->numCols * (float)sky->numCols;
+        int ySky = yPt[i] / (float)image->numRows * (float)sky->numRows;
+
+        fPt[i] = medianSd->sampleMedian - sky->data.F32[ySky][xSky];
+        dfPt[i] = 1.0 / medianSd->sampleStdev;
+
+        psTrace("psModules.detrend", 7, "[%d:%d,%d:%d]: %f %f\n", (int)region.x0, (int)region.x1,
+                (int)region.y0, (int)region.y1, fPt[i], dfPt[i]);
+    }
+    psFree(sky);
+    psFree(median);
+    psFree(medianSd);
+
+    return measurements;
+}
+
+bool pmFringeStatsWriteFits(psFits *fits,
+                            psMetadata *header,
+                            const pmFringeStats *fringe,
+                            const char *extname
+                           )
+{
+    // Make sure the input is well-behaved
+    PS_ASSERT_PTR_NON_NULL(fits, false);
+    PS_ASSERT_PTR_NON_NULL(fringe, false);
+    pmFringeRegions *regions = fringe->regions; // The fringe regions
+    PS_ASSERT_PTR_NON_NULL(regions, false);
+    int numRows = regions->nRequested;  // Number of rows in the table
+    PS_ASSERT_INT_POSITIVE(numRows, false);
+    psVector *f = fringe->f;            // The fringe measurements
+    psVector *df = fringe->df;      // The fringe standard deviatiations
+    PS_ASSERT_VECTOR_NON_NULL(f, false);
+    PS_ASSERT_VECTOR_NON_NULL(df, false);
+    PS_ASSERT_VECTOR_TYPE(f, PS_TYPE_F32, false);
+    PS_ASSERT_VECTOR_TYPE(df, PS_TYPE_F32, false);
+    PS_ASSERT_VECTOR_SIZE(f, (long)numRows, false);
+    PS_ASSERT_VECTOR_SIZE(df, (long)numRows, false);
+
+    // We need to write:
+    // Vectors: f, df
+    psArray *table = psArrayAlloc(numRows); // The table
+    table->n = numRows;
+    // Translate the vectors into the required format for psFitsWriteTable()
+    for (long i = 0; i < numRows; i++) {
+        psMetadata *row = psMetadataAlloc();
+        psMetadataAddF32(row, PS_LIST_TAIL, "f", PS_META_REPLACE, "Fringe measurement", f->data.F32[i]);
+        psMetadataAddF32(row, PS_LIST_TAIL, "df", PS_META_REPLACE, "Fringe stdev", df->data.F32[i]);
+        table->data[i] = row;
+    }
+
+    if (!psFitsWriteTable(fits, header, table, extname)) {
+        psError(PS_ERR_IO, false, "Unable to write fringe data to extension %s\n", extname);
+        psFree(table);
+        return false;
+    }
+
+    psFree(table);
+    return true;
+}
+
+pmFringeStats *pmFringeStatsReadFits(psMetadata *header, const psFits *fits, const char *extname,
+                                     pmFringeRegions *regions)
+{
+    PS_ASSERT_PTR_NON_NULL(fits, NULL);
+    PS_ASSERT_PTR_NON_NULL(regions, NULL);
+    PS_ASSERT_INT_POSITIVE(regions->nRequested, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(regions->x, regions->y, NULL);
+    PS_ASSERT_VECTOR_SIZE(regions->x, (long)regions->nRequested, NULL);
+
+    if (extname && strlen(extname) > 0) {
+        if (!psFitsMoveExtName(fits, extname)) {
+            psError(PS_ERR_IO, false, "Unable to move to extension %s\n", extname);
+            return NULL;
+        }
+    } else if (!psFitsMoveExtNum(fits, 0, false)) {
+        psError(PS_ERR_IO, false, "Unable to move to PHU\n");
+        return NULL;
+    }
+
+    psMetadata *headerCopy = psMemIncrRefCounter(header); // Copy of the header, or NULL
+
+    headerCopy = psFitsReadHeader(headerCopy, fits); // The FITS header
+    if (!headerCopy) {
+        psError(PS_ERR_IO, false, "Unable to read header for extension %s\n", extname);
+        psFree(headerCopy);
+        return NULL;
+    }
+    psFree(headerCopy);
+
+    // Now the vectors: f, df
+    psArray *table = psFitsReadTable(fits); // The table
+    long numRows = table->n;            // Number of rows
+
+    pmFringeStats *fringes = pmFringeStatsAlloc(regions); // The fringe measurements
+    psVector *f = fringes->f;           // fringe measurement
+    psVector *df = fringes->df;         // fringe stdev
+
+    #define READ_STATS_ROW(VECTOR, TYPE, NAME, DESCRIPTION) \
+    VECTOR->data.TYPE[i] = psMetadataLookup##TYPE(&mdok, row, NAME); \
+    if (!mdok) { \
+        psError(PS_ERR_IO, true, "Unable to find " #DESCRIPTION " .\n"); \
+        psFree(table); \
+        psFree(fringes); \
+        return NULL; \
+    }
+
+    // Translate the table into vectors
+    bool mdok;                          // Status of MD lookup
+    for (long i = 0; i < numRows; i++) {
+        psMetadata *row = table->data[i]; // Table row
+        READ_STATS_ROW(f, F32, "f", "fringe measurement");
+        READ_STATS_ROW(df, F32, "df", "fringe standard deviation");
+    }
+    psFree(table);
+
+    return fringes;
+}
+
+
+pmFringeStats *pmFringeStatsConcatenate(const psArray *fringes, const psVector *x0, const psVector *y0)
+{
+    PS_ASSERT_PTR_NON_NULL(fringes, NULL);
+    PS_ASSERT_PTR_NON_NULL(fringes->data, NULL);
+    PS_ASSERT_INT_POSITIVE(fringes->n, NULL);
+    if (x0 && y0) {
+        PS_ASSERT_VECTOR_NON_NULL(x0, NULL);
+        PS_ASSERT_VECTOR_NON_NULL(y0, NULL);
+        PS_ASSERT_VECTOR_TYPE(x0, PS_TYPE_S32, NULL);
+        PS_ASSERT_VECTOR_TYPE(y0, PS_TYPE_S32, NULL);
+        PS_ASSERT_VECTORS_SIZE_EQUAL(x0, y0, NULL);
+        PS_ASSERT_VECTOR_SIZE(x0, fringes->n, NULL);
+        PS_ASSERT_VECTOR_SIZE(y0, fringes->n, NULL);
+    }
+
+    // Get the measurement parameters, and check they are consistent
+    int numPoints = 0;                  // Number of fringe points
+    int dX = 0, dY = 0;                 // Half-width and -height of fringe boxes
+    int nX = 0, nY = 0;                 // Smoothing scales
+    for (long i = 0; i < fringes->n; i++) {
+        pmFringeStats *fringe = fringes->data[i]; // The fringe of interest
+        pmFringeRegions *regions = fringe->regions; // The fringe regions
+        if (numPoints == 0) {
+            dX = regions->dX;
+            dY = regions->dY;
+            nX = regions->nX;
+            nY = regions->nY;
+        } else if (regions->dX != dX || regions->dY != dY || regions->nX != nX || regions->nY != nY) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Fringe %ld has different parameters (%d,%d,%d,%d) "
+                    "from the first (%d,%d,%d,%d).\n", i,
+                    regions->dX, regions->dY, regions->nX, regions->nY, dX, dY, nX, nY);
+            return NULL;
+        }
+        unsigned int num = regions->nRequested;  // Number of fringe points
+        if (regions->x->n != num || regions->y->n != num || regions->mask->n != num ||
+                fringe->f->n != num || fringe->df->n != num) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Length of region (%ld,%ld,%ld) and fringe vectors "
+                    "do not match (%ld,%ld) with the official value (%d).\n", regions->x->n, regions->y->n,
+                    regions->mask->n, fringe->f->n, fringe->df->n, num);
+            return NULL;
+        }
+        numPoints += regions->nRequested;
+    }
+
+    pmFringeRegions *newRegions = pmFringeRegionsAlloc(numPoints, dX, dY, nX, nY); // The new list of regions
+    pmFringeStats *newStats = pmFringeStatsAlloc(newRegions); // The new list of statistics
+
+    long offset = 0;                    // Offset from start of the list
+    for (long i = 0; i < fringes->n; i++) {
+        pmFringeStats *fringe = fringes->data[i]; // The fringe of interest
+        pmFringeRegions *regions = fringe->regions; // The fringe regions
+        // Copy the data over
+        memcpy(&newRegions->x->data.F32[offset], regions->x->data.F32, regions->x->n * sizeof(psF32));
+        memcpy(&newRegions->y->data.F32[offset], regions->y->data.F32, regions->y->n * sizeof(psF32));
+        memcpy(&newRegions->mask->data.U8[offset], regions->mask->data.U8, regions->mask->n * sizeof(psU8));
+        memcpy(&newStats->f->data.F32[offset], fringe->f->data.F32, fringe->f->n * sizeof(psF32));
+        memcpy(&newStats->df->data.F32[offset], fringe->f->data.F32, fringe->df->n * sizeof(psF32));
+        offset += regions->nRequested;
+    }
+
+    psFree(newRegions);                 // Drop reference
+    return newStats;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// pmFringeScale
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static void fringeScaleFree(pmFringeScale *scale)
+{
+    psFree(scale->coeff);
+    psFree(scale->coeffErr);
+    return;
+}
+
+pmFringeScale *pmFringeScaleAlloc(int nFringeFrames)
+{
+    pmFringeScale *scale = psAlloc(sizeof(pmFringeScale));
+    (void)psMemSetDeallocator(scale, (psFreeFunc)fringeScaleFree);
+
+    scale->nFringeFrames = nFringeFrames;
+    scale->coeff = psVectorAlloc(nFringeFrames + 1, PS_TYPE_F32);
+    scale->coeffErr = psVectorAlloc(nFringeFrames + 1, PS_TYPE_F32);
+    scale->coeff->n = nFringeFrames + 1;
+    scale->coeff->n = nFringeFrames + 1;
+
+    return scale;
+}
+
+// Determine the fringe scales through solving the least-squares problem
+static bool scaleMeasure(pmFringeScale *scale, // Scale to return
+                         pmFringeStats *science, // The fringe measurements for the science image
+                         psArray *fringes // Array of fringe measurements for the templates
+                        )
+{
+    assert(scale);
+    assert(science);
+    assert(fringes);
+    assert(scale->nFringeFrames == fringes->n);
+
+    psVector *mask = science->regions->mask; // The region mask
+
+    int numCoeffs = fringes->n + 1;     // Number of coefficients: scales for the templates plus a background
+    int numPoints = science->regions->nRequested; // Number of points (i.e., fringe measurements)
+
+    psImage *A = psImageAlloc(numCoeffs, numCoeffs, PS_TYPE_F64); // The least-squares matrix
+    psVector *B = psVectorAlloc(numCoeffs, PS_TYPE_F64); // The least-squares vector
+    B->n = numCoeffs;
+
+    // Generate the least-squares matrix and vector
+    for (int i = 0; i < numCoeffs; i++) {
+        psVector *fringe1 = NULL;       // A fringe measurement
+        if (i != 0) {
+            pmFringeStats *fringe = fringes->data[i - 1];
+            fringe1 = fringe->f;
+        }
+
+        // Fill in the upper part of the matrix
+        for (int j = i; j < numCoeffs; j++) {
+            psVector *fringe2 = NULL;   // Another fringe measurement
+            if (j != 0) {
+                pmFringeStats *fringe = fringes->data[j - 1];
+                fringe2 = fringe->f;
+            }
+
+            double matrix = 0.0;        // The matrix sum
+            for (int k = 0; k < numPoints; k++) {
+                if (!mask->data.U8[k]) {
+                    psF32 f1 = (fringe1) ? fringe1->data.F32[k] : 1.0; // Contribution from i fringe
+                    psF32 f2 = (fringe2) ? fringe2->data.F32[k] : 1.0; // Contribution from j fringe
+                    psF32 dsInv = science->df->data.F32[k]; // 1 / sigma
+                    matrix += f1 * f2 * dsInv * dsInv;
+                }
+            }
+            A->data.F64[i][j] = matrix;
+        }
+
+        // Use symmetry to fill in the lower part of the matrix
+        for (int j = 0; j < i; j++) {
+            A->data.F64[i][j] = A->data.F64[j][i];
+        }
+
+        double vector = 0.0;            // The vector sum
+        for (int k = 0; k < numPoints; k++) {
+            if (!mask->data.U8[k]) {
+                psF32 f1 = (fringe1) ? fringe1->data.F32[k] : 1.0; // Contribution from fringe 1
+                psF32 s = science->f->data.F32[k]; // Contribution from science measurement
+                psF32 dsInv = science->df->data.F32[k]; // 1 / sigma
+                vector += f1 * s * dsInv * dsInv;
+            }
+        }
+        B->data.F64[i] = vector;
+    }
+
+    if (psTraceGetLevel(__func__) >= 5) {
+        printf("From %d points:\n", numPoints);
+        for (int i = 0; i < numCoeffs; i++) {
+            for (int j = 0; j < numCoeffs; j++) {
+                printf("%.2e ", A->data.F64[i][j]);
+            }
+            printf("\n");
+        }
+    }
+
+    // Solve the least-squares equation
+    if (! psMatrixGJSolve(A, B)) {
+        psError(PS_ERR_UNKNOWN, false, "Could not solve linear equations.  Returning NULL.\n");
+        return false;
+    }
+
+    // Copy the results over
+    for (int i = 0; i < numCoeffs; i++) {
+        scale->coeff->data.F32[i] = B->data.F64[i];
+        scale->coeffErr->data.F32[i] = sqrt(A->data.F64[i][i]);
+    }
+
+    return true;
+}
+
+// Measure the fringe differences for each region
+static bool fringeScaleDiffs(psVector *diff, // Vector of differences
+                             pmFringeStats *science, // Science fringe measurements
+                             psArray *fringes, // Template fringe measurements
+                             pmFringeScale *scale // Fringe scales
+                            )
+{
+    assert(diff);
+    assert(diff->type.type == PS_TYPE_F32);
+    assert(science);
+    assert(fringes);
+    assert(scale);
+    assert(diff->n == science->regions->nRequested);
+    assert(fringes->n == scale->nFringeFrames);
+
+    psVector *mask = science->regions->mask; // The region mask
+
+    for (int i = 0; i < diff->n; i++) {
+        if (!mask->data.U8[i]) {
+            float difference = science->f->data.F32[i] - scale->coeff->data.F32[0];
+            for (int j = 0; j < fringes->n; j++) {
+                pmFringeStats *fringe = fringes->data[j]; // The fringe of interest
+                difference -= scale->coeff->data.F32[j + 1] * fringe->f->data.F32[i];
+            }
+            diff->data.F32[i] = difference * difference * science->df->data.F32[i] * science->df->data.F32[i];
+        }
+    }
+
+    return true;
+}
+
+// Clip regions based on the differences; return the number masked
+static int clipRegions(psVector *diffs, // Differences
+                       psVector *mask,  // Region mask
+                       float rej        // Rejection limit in standard deviations
+                      )
+{
+    assert(diffs);
+    assert(diffs->type.type == PS_TYPE_F32);
+    assert(mask);
+    assert(mask->type.type == PS_TYPE_U8);
+    assert(diffs->n == mask->n);
+
+    psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_QUARTILE); // Statistics
+    psVectorStats(stats, diffs, NULL, mask, 1);
+    float middle = stats->sampleMedian; // The middle of the distribution
+    float thresh = rej * 0.74 * (stats->sampleUQ - stats->sampleLQ); // The rejection threshold
+    psFree(stats);
+
+    int numClipped = 0;                 // Number clipped
+    for (int i = 0; i < diffs->n; i++) {
+        psTrace("psModules.detrend", 10, "Region %d (%d): %f\n", i, mask->data.U8[i], diffs->data.F32[i]);
+        if (!mask->data.U8[i] && fabs(diffs->data.F32[i]) > middle + thresh) {
+            psTrace("psModules.detrend", 5, "Masking %d: %f\n", i, diffs->data.F32[i]);
+            mask->data.U8[i] = 1;
+            numClipped++;
+        }
+    }
+
+    return numClipped;
+}
+
+
+// XXX include the fringe error (fringe->df) in the fit?
+pmFringeScale *pmFringeScaleMeasure(pmFringeStats *science, psArray *fringes, float rej,
+                                    unsigned int nIter, float keepFrac)
+{
+    PS_ASSERT_PTR_NON_NULL(science, NULL);
+    PS_ASSERT_PTR_NON_NULL(fringes, NULL);
+    PS_ASSERT_INT_POSITIVE(fringes->n, NULL);
+    PS_ASSERT_INT_POSITIVE(nIter, NULL);
+
+    pmFringeRegions *regions = science->regions; // The fringe regions
+    int numRegions = regions->nRequested; // Number of regions
+
+    // Ensure we are dealing with the SAME fringe points for all the inputs.
+    // Otherwise, we're going to get crazy results.
+    for (long i = 0; i < numRegions; i++) {
+        float xScience = regions->x->data.F32[i]; // The x position for the science image
+        float yScience = regions->y->data.F32[i]; // The y position for the science image
+        for (long j = 0; j < fringes->n; j++) {
+            pmFringeStats *fringe = fringes->data[j]; // The fringe statistics from a fringe image
+            pmFringeRegions *fringeRegions = fringe->regions; // The fringe regions for that fringe image
+            if (fringeRegions->x->data.F32[i] != xScience ||
+                    fringeRegions->y->data.F32[i] != yScience) {
+                psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Science and fringe measurement regions "
+                        "don't match.\n");
+                return NULL;
+            }
+        }
+    }
+
+    // Set up the mask
+    if (!regions->mask) {
+        regions->mask = psVectorAlloc(numRegions, PS_TYPE_U8);
+        regions->mask->n = numRegions;
+        psVectorInit(regions->mask, 0);
+    }
+    psVector *mask = regions->mask;     // The region mask
+    psStats *median = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN); // Median statistics
+    unsigned int numClipped = 0;        // Total number clipped
+    psVector *diff = psVectorAlloc(numRegions, PS_TYPE_F32); // The differences between obs. and pred.
+    diff->n = numRegions;
+
+    pmFringeScale *scale = pmFringeScaleAlloc(fringes->n); // The fringe scales
+
+    // Get rid of bad data points
+    for (int i = 0; i < fringes->n; i++) {
+        pmFringeStats *fringe = fringes->data[i]; // The fringe of interest
+        for (int j = 0; j < numRegions; j++) {
+            if (!finite(fringe->f->data.F32[j])) {
+                mask->data.U8[j] = 1;
+                psTrace("psModules.detrend", 9, "Masking region %d because not finite in fringe %d.\n", j, i);
+            }
+        }
+    }
+
+    // Get rid of the extreme outliers by assuming most of the points are somewhat clustered
+    psVectorStats(median, science->f, NULL, NULL, 0);
+    scale->coeff->data.F32[0] = median->sampleMedian;
+    for (int i = 0; i < fringes->n; i++) {
+        pmFringeStats *fringe = fringes->data[i]; // The fringe of interest
+        psVectorStats(median, fringe->f, NULL, NULL, 0);
+        scale->coeff->data.F32[0] -= median->sampleMedian;
+        scale->coeff->data.F32[i] = 0.0;
+    }
+    psFree(median);
+    fringeScaleDiffs(diff, science, fringes, scale);
+    numClipped = clipRegions(diff, mask, 3.0*rej);
+    psTrace("psModules.detrend", 4, "%d regions clipped in initial pass.\n", numClipped);
+
+    unsigned int iter = 0;              // Iteration number
+    unsigned int iterClip = 0;          // Number clipped in this iteration
+    do {
+        iter++;
+        scaleMeasure(scale, science, fringes); // The scales
+        psTrace("psModules.detrend", 1, "Fringe scales after iteration %d:\n", iter);
+        psTrace("psModules.detrend", 1, "Background: %f %f\n", scale->coeff->data.F32[0], scale->coeffErr->data.F32[0]);
+        for (int i = 0; i < scale->nFringeFrames; i++) {
+            psTrace("psModules.detrend", 1, "%d: %f %f\n", i, scale->coeff->data.F32[i + 1],
+                    scale->coeffErr->data.F32[i + 1]);
+        }
+
+        fringeScaleDiffs(diff, science, fringes, scale);
+        iterClip = clipRegions(diff, mask, rej); // Number clipped
+        numClipped += iterClip;
+        psTrace("psModules.detrend", 9, "Clipped: %d\tFrac: %f\n", iterClip, (float)numClipped/(float)numRegions);
+    } while (iterClip > 0 && iter < nIter && (float)numClipped/(float)numRegions <= 1.0 - keepFrac);
+    psFree(diff);
+
+    // A final iteration with the last clipping
+    scaleMeasure(scale, science, fringes);
+
+    return scale;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Fringe correction
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// XXX note that this modifies the input fringe images
+psImage *pmFringeCorrect(pmReadout *readout, pmFringeRegions *fringes, psArray *fringeImages,
+                         psArray *fringeStats, psMaskType maskVal, float rej,
+                         unsigned int nIter, float keepFrac)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, NULL);
+    PS_ASSERT_PTR_NON_NULL(readout->image, NULL);
+    PS_ASSERT_IMAGE_NON_EMPTY(readout->image, NULL);
+    PS_ASSERT_PTR_NON_NULL(fringes, NULL);
+    PS_ASSERT_PTR_NON_NULL(fringeImages, NULL);
+    PS_ASSERT_PTR_NON_NULL(fringeStats, NULL);
+    PS_ASSERT_INT_EQUAL(fringeImages->n, fringeStats->n, NULL);
+    PS_ASSERT_INT_POSITIVE(nIter, NULL);
+
+    // measure the fringe stats for the science frame and solve for the scales
+    pmFringeStats *scienceStats = pmFringeStatsMeasure(fringes, readout, maskVal);
+
+    if (psTraceGetLevel(__func__) > 9) {
+        for (int i = 0; i < fringes->nRequested; i++) {
+            printf("%f", scienceStats->f->data.F32[i]);
+            for (int j = 0; j < fringeStats->n; j++) {
+                pmFringeStats *fringe = fringeStats->data[j];
+                printf("\t%f", fringe->f->data.F32[i]);
+            }
+            printf("\n");
+        }
+    }
+
+    pmFringeScale *scale = pmFringeScaleMeasure(scienceStats, fringeStats, rej, nIter, keepFrac);
+    psFree(scienceStats);
+
+    psTrace("psModules.detrend", 7, "Fringe solution:\n");
+    for (int i = 0; i < fringeImages->n + 1; i++) {
+        psTrace("psModules.detrend", 7, "%d: %f %f\n", i, scale->coeff->data.F32[i],
+                scale->coeffErr->data.F32[i]);
+    }
+
+    // build the fringe correction image
+    // XXX we could save data space by making the first image the output image
+    psImage *sumFringe = psImageAlloc(readout->image->numCols, readout->image->numRows, PS_TYPE_F32);
+    //psBinaryOp(sumFringe, sumFringe, "+", psScalarAlloc(scale->coeff->data.F32[0], PS_TYPE_F32));
+    for (int i = 0; i < fringeImages->n; i++) {
+
+        // rescale the fringe image
+        psBinaryOp(fringeImages->data[i], fringeImages->data[i], "*",
+                   psScalarAlloc(scale->coeff->data.F32[i+1], PS_TYPE_F32));
+
+        // sum together
+        sumFringe = (psImage*)psBinaryOp(sumFringe, sumFringe, "+", fringeImages->data[i]);
+    }
+    psFree(scale);
+
+    // subtract the resulting fringe frame
+    readout->image = (psImage*)psBinaryOp(readout->image, readout->image, "-", sumFringe);
+
+    return sumFringe;
+}
+
Index: /tags/RHL_v1_1/psModules/src/detrend/pmFringeStats.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmFringeStats.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmFringeStats.h	(revision 9533)
@@ -0,0 +1,177 @@
+/** @file  pmFringeStats.h
+ *
+ *  @brief Measure Fringe statistics and apply correction
+ *
+ *  @author Eugene Magnier, IfA
+ *
+ *  @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-07-12 04:44:01 $
+ *
+ *  Copyright 2004 IfA, University of Hawaii
+ */
+
+# ifndef PM_FRINGE_STATS
+# define PM_FRINGE_STATS
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// pmFringeRegions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/** Structure to hold the fringe measurement regions.
+ */
+typedef struct
+{
+    psU32 nRequested;   // number of fringe points selected
+    psU32 nAccepted;   // number of fringe points not masked
+    psU32 dX;    // median box half-width
+    psU32 dY;    // median box half-height
+    psU32 nX;    // large-scale smoothing in x (col)
+    psU32 nY;    // large-scale smoothing in y (row)
+    psVector *x;    // fringe point coordinates (col)
+    psVector *y;    // fringe point coordinates (row)
+    psVector *mask;   // fringe point on/off mask
+}
+pmFringeRegions;
+
+/// Allocate fringe regions
+pmFringeRegions *pmFringeRegionsAlloc (
+    int nPts,     // number of points to create
+    int dX,     // half-width of fringe boxes
+    int dY,     // half-height of fringe boxes
+    int nX,     // smoothing scale in x
+    int nY    // smoothing scale in y
+);
+
+// Generate the fringe points
+bool pmFringeRegionsCreatePoints(pmFringeRegions *fringe, // Fringe regions
+                                 const psImage *image, // Image for the regions (defines the size)
+                                 psRandom *random // Random number generator
+                                );
+
+// Write the regions to a FITS file
+bool pmFringeRegionsWriteFits(psFits *fits, // Output FITS file
+                              psMetadata *header, // Header to write, or NULL
+                              const pmFringeRegions *regions, // Regions to write
+                              const char *extname // Extension name, or NULL
+                             );
+
+// Read the regions from a FITS file
+pmFringeRegions *pmFringeRegionsReadFits(psMetadata *header, // Header to read, or NULL
+        const psFits *fits, // Input FITS file
+        const char *extname // Extension name, or NULL
+                                        );
+
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// pmFringeStats
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/** Structure to hold the fringe measurements for a particular image
+ */
+typedef struct
+{
+    pmFringeRegions *regions;           // Fringe regions
+    psVector *f;    // fringe point median
+    psVector *df;   // fringe point stdev
+}
+pmFringeStats;
+
+/// Allocate fringe statistics
+pmFringeStats *pmFringeStatsAlloc(pmFringeRegions *regions // The fringe regions which will be measured
+                                 );
+
+/** Measure the fringe stats for an image
+ *
+ *  Given an input image and a fringe stats structure, measure the statistics for each of the fringe points on
+ *  the image.  If the fringe vectors are NULL, new vectors are created.
+ *
+ *  XXX should the structure carry the image dimensions for validation?
+ *
+ *  @return  bool: True or false for success or failure
+ */
+pmFringeStats *pmFringeStatsMeasure(
+    pmFringeRegions *fringe,            ///< fringe regions
+    pmReadout *readout,                 ///< measure fringes on this readout
+    psMaskType maskVal                  ///< Mask value for statistics
+);
+
+// Write the fringe stats for an image to a FITS table
+bool pmFringeStatsWriteFits(psFits *fits, // FITS file to which to write
+                            psMetadata *header, // Header to write, or NULL
+                            const pmFringeStats *fringe, // Fringe statistics to be written
+                            const char *extname // Extension name for table
+                           );
+
+// Read the fringe stats for an image from a FITS table
+pmFringeStats *pmFringeStatsReadFits(psMetadata *header, // Header to read, or NULL
+                                     const psFits *fits, // FITS file from which to read
+                                     const char *extname, // Extension name to read
+                                     pmFringeRegions *regions // Corresponding regions for fringe measurements
+                                    );
+
+// Concatenate the fringe stats for several readouts into a single fringe stats.  The idea is that each
+// readout of each chip should be measured separately (so as to avoid any gaps, as in the case for GPC), but
+// then perform the fit to all the readouts belonging to a chip.  To do so, we need to concatenate.
+pmFringeStats *pmFringeStatsConcatenate(const psArray *fringes, // Array of pmFringeStats for the readouts
+                                        const psVector *x0, // Offset in x for the readout
+                                        const psVector *y0 // Offset in y for the readout
+                                       );
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// pmFringeScale
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/** the pmFringeScale structure defines the relationship between two fringe measurements
+ */
+typedef struct
+{
+    int nFringeFrames;
+    psVector *coeff;
+    psVector *coeffErr;
+}
+pmFringeScale;
+
+/** Determine the scales for the fringe correction
+ *
+ * Given an input fringe measurement, and an array of template fringe measurements, measure the contribution
+ * of each of the templates to the input.  Rejection is performed on the fringe regions, to weed out stars
+ * etc.
+ *
+ * @return pmFringeScale*
+ */
+pmFringeScale *pmFringeScaleMeasure(pmFringeStats *science, // Fringe measurements from science image
+                                    psArray *fringes, // Array of fringe measurements from templates
+                                    float rej, // Rejection threshold (in standard deviations)
+                                    unsigned int nIter, // Maximum number of iterations
+                                    float keepFrac // Minimum fraction of regions to keep
+                                   );
+
+/// Allocate fringe scales
+pmFringeScale *pmFringeScaleAlloc(int nFringeFrames // Number of fringe frames
+                                 );
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Fringe correction
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/** Fringe correct the science image
+ *
+ *  Given a science image, a set of fringe stats and a matched set of fringe images, correct the science image
+ *  for the fringe images.
+ *
+ *  @return  psImage: summed fringe image
+ */
+psImage *pmFringeCorrect(pmReadout *in, // place info about results here
+                         pmFringeRegions *fringes, // The fringe regions used
+                         psArray *fringeImages, // fringe images to use in correction
+                         psArray *fringeStats, // fringe stats to use in correction
+                         psMaskType maskVal, // Value to mask
+                         float rej,     // Rejection threshold, for pmFringeScaleMeasure
+                         unsigned int nIter, // Maximum number of iterations, for pmFringeScaleMeasure
+                         float keepFrac // Minimum fraction of regions to keep, for pmFringeScaleMeasure
+                        );
+
+
+# endif
Index: /tags/RHL_v1_1/psModules/src/detrend/pmMaskBadPixels.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmMaskBadPixels.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmMaskBadPixels.c	(revision 9533)
@@ -0,0 +1,125 @@
+/** @file  pmMaskBadPixels.c
+ *
+ *  @brief Given an input image, a bad pixel mask, a corresponding value in the bad pixel mask to mask, a
+ *  saturation level, and a growing radius, mask in the input image those pixels in the bad pixel mask that
+ *  match the value to mask.
+ *
+ *  Apply the supplied bad pixel mask readout (mask) to the given science readout (input).
+ *  The science readout must already have a supplied mask element (use eg. pmReadoutSetMask)
+ *  The supplied mask image must be of MASK type
+ 
+ *  If maskVal is non-zero, all pixels in the mask have any of the same bits sets as maskVal
+ *  shall have the corresponding bits raised.
+ 
+ *  If maskVal is zero, any zero pixels in the mask be OR-ed with PM_MASK_BAD 
+ 
+ * Note that the input image and the mask need not be the same size, since the input image may
+ * already have been trimmed (following overscan subtraction).  The function assumes the pixels
+ * originate from the same detector and uses the values of col0,row0 in both the input and the
+ * mask to match corresponding pixels.
+ 
+ * In the event that the mask image is too small (i.e., pixels on the input image refer to
+ * pixels outside the range of the mask image), the function shall generate an error.
+ 
+ *  @author Ross Harman, MHPCC
+ *  @author Eugene Magnier, IfA
+ *
+ *  @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-06-10 02:59:23 $
+ *
+ *  Copyright 2004 IfA, University of Hawaii
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <strings.h>
+
+#include "pmFPAMaskWeight.h"
+#include "pmMaskBadPixels.h"
+#include "pmMaskBadPixelsErrors.h"
+
+// apply an externally-supplied mask image to the current science image
+// this function
+bool pmMaskBadPixels(pmReadout *input, pmReadout *mask, psMaskType maskVal)
+{
+
+    int xI;
+    int xJ;
+
+    PS_ASSERT_PTR_NON_NULL(input, false);
+    PS_ASSERT_PTR_NON_NULL(input->mask, false);
+    PS_ASSERT_IMAGE_TYPE(input->mask, PS_TYPE_MASK, NULL);
+
+    PS_ASSERT_PTR_NON_NULL(mask, false);
+    PS_ASSERT_PTR_NON_NULL(mask->mask, false);
+    PS_ASSERT_IMAGE_TYPE(mask->mask, PS_TYPE_MASK, NULL);
+
+    psImage *inMask = input->mask;
+    psImage *exMask = mask->mask;
+
+    int rowMax = inMask->row0 + inMask->numRows;
+    int colMax = inMask->col0 + inMask->numCols;
+
+    if (exMask->row0 > inMask->row0) {
+        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_SIZE_INPUT_IMAGE,
+                 inMask->numRows, inMask->numCols, exMask->numRows, exMask->numCols);
+        return false;
+    }
+    if (exMask->col0 > inMask->col0) {
+        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_SIZE_INPUT_IMAGE,
+                 inMask->numRows, inMask->numCols, exMask->numRows, exMask->numCols);
+        return false;
+    }
+    if (exMask->row0 + exMask->numRows < rowMax) {
+        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_SIZE_INPUT_IMAGE,
+                 inMask->numRows, inMask->numCols, exMask->numRows, exMask->numCols);
+        return false;
+    }
+    if (exMask->col0 + exMask->numCols < colMax) {
+        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_SIZE_INPUT_IMAGE,
+                 inMask->numRows, inMask->numCols, exMask->numRows, exMask->numCols);
+        return false;
+    }
+
+    // Determine total offset based on image offset with chip offset
+    // XXX if we choose to correct for the readout location, apply input->col0,row0 here
+    int offCol = inMask->col0 - exMask->col0;
+    int offRow = inMask->row0 - exMask->row0;
+
+    // masks are both of type PS_TYPE_MASK
+    psMaskType **exVal = exMask->data.U8;
+    psMaskType **inVal = inMask->data.U8;
+
+    // apply exMask values
+    if (maskVal) {
+        // set raised pixels in exMask which are selected by maskVal
+        for (int j = 0; j < inMask->numRows; j++) {
+            xJ = j - offRow;
+            for (int i = 0; i < inMask->numCols; i++) {
+                xI = i - offCol;
+                inVal[j][i] |= (maskVal & exVal[xJ][xI]);
+            }
+        }
+    } else {
+        // set raised pixels in exMask which are selected by maskVal
+        for (int j = 0; j < inMask->numRows; j++) {
+            xJ = j - offRow;
+            for (int i = 0; i < inMask->numCols; i++) {
+                xI = i - offCol;
+                if (exVal[xJ][xI] == 0) {
+                    inVal[j][i] |= PM_MASK_BAD;
+                }
+            }
+        }
+    }
+
+    return true;
+}
Index: /tags/RHL_v1_1/psModules/src/detrend/pmMaskBadPixels.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmMaskBadPixels.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmMaskBadPixels.h	(revision 9533)
@@ -0,0 +1,40 @@
+/** @file  pmMaskBadPixels.h
+ *
+ *  @brief Given an input image, a bad pixel mask, a corresponding value in the bad pixel mask to mask, a
+ *  saturation level, and a growing radius, mask in the input image those pixels in the bad pixel mask that
+ *  match the value to mask.
+ *
+ *  Given an input image, in, a bad pixel mask, a corresponding value in the bad pixel mask to mask in the
+ *  input image, maskVal, a saturation level, and a growing radius, pmMaskBadPixels shall mask in the input
+ *  image those pixels in the bad pixel mask that match the value to mask. Note that the input image, in, is
+ *  modified in-place. All pixels in the mask which satisfy the maskVal shall have their corresponding pixels
+ *  masked in the input image, in. All pixels which satisfy the growVal shall have their corresponding
+ *  pixels, along with all pixels within the grow radius masked. Pixels which have flux greater than sat shall
+ *  also be masked, but not grown. Note that the input image, in, and the mask need not be the same size,
+ *  since the input image may already have been trimmed (following overscan subtraction), but the function
+ *  shall use the offsets in the image (in->x0 and in->y0) to determine the appropriate offsets to obtain the
+ *  correct pixel on the mask. In the event that the mask image is too small (i.e., pixels on the input image
+ *  refer to pixels outside the range of the mask image), the function shall generate an error.
+ *
+ *  @author Ross Harman, MHPCC
+ *
+ *  @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-06-10 02:59:23 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#ifndef PM_MASK_BAD_PIXELS_H
+#define PM_MASK_BAD_PIXELS_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+/** Execute bad pixels module.
+ *
+ *
+ *  @return  bool: True or false for success or failure
+ */
+
+bool pmMaskBadPixels(pmReadout *input, pmReadout *mask, psMaskType maskVal);
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/detrend/pmMaskBadPixelsErrors.dat
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmMaskBadPixelsErrors.dat	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmMaskBadPixelsErrors.dat	(revision 9533)
@@ -0,0 +1,20 @@
+#
+#  This file is used to generate pmMaskBadPixelsErrors.h content
+#
+#  Format is:
+#  ERRORNAME(one word)    ERRORTEXT
+#
+#  N.B. in code, the ERRORNAME appears as PS_ERRORTEXT_ERRORNAME
+####################################################################
+#
+pmMaskBadPixels_NULL_MASK_IMAGE            Null not allowed for mask image.
+pmMaskBadPixels_NULL_INPUT_IMAGE           Null not allowed for input image.
+pmMaskBadPixels_SIZE_INPUT_IMAGE           Input image size exceeds that of mask image: (%d, %d) vs (%d, %d)
+pmMaskBadPixels_SIZE_MASK_IMAGE            Input image mask size exceeds that of mask image: (%d, %d) vs (%d, %d)
+pmMaskBadPixels_OFFSET_MASK_IMAGE          Total offset >= mask image: (%d, %d) vs (%d, %d)
+pmMaskBadPixels_OFFSET_INPUT_IMAGE         Total offset >= input image: (%d, %d) vs (%d, %d)
+pmMaskBadPixels_OFFSET_INPUT_IMAGE_MASK    Total offset >= input image mask: (%d, %d) vs (%d, %d)
+pmMaskBadPixels_TYPE_INPUT_IMAGE           Complex types not allowed for input image. Type: %d
+pmMaskBadPixels_TYPE_MASK_IMAGE            Mask must be PS_TYPE_MASK type. Type: %d
+pmMaskBadPixels_TYPE_MISMATCH              Input and flat image types differ: (%d vs %d)
+pmMaskBadPixels_TYPE_UNSUPPORTED           Unsupported image datatype. Type: %d
Index: /tags/RHL_v1_1/psModules/src/detrend/pmMaskBadPixelsErrors.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmMaskBadPixelsErrors.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmMaskBadPixelsErrors.h	(revision 9533)
@@ -0,0 +1,50 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/** @file  pmMaskBadPixelsErrors.h
+ *
+ *  @brief Contains the error text for the mask bad pixels module
+ *
+ *  @ingroup ErrorHandling
+ *
+ *  @author Ross Harman, MHPCC
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:10:08 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#ifndef PM_FLATFIELD_ERRORS_H
+#define PM_FLATFIELD_ERRORS_H
+
+/* N.B., lines between '//~Start' and '//~End' are automatic generated from
+ * the template following the '//~Start'.  The template is used to generate
+ * the other lines by, for each error text in pmMaskBadPixelsErrors.dat, the following
+ * substitutions are made:
+ *     $1  The error text macro name (first word in the pmMaskBadPixelsErrors.h lines)
+ *     $2  The error text (rest of the line in pmMaskBadPixelsErrors.h)
+ *     $n  The order of the source line in pmMaskBadPixelsErrors.h (comments excluded)
+ *
+ * DO NOT EDIT THE LINES BETWEEN //~Start and //~End!  ANY CHANGES WILL BE OVERWRITTEN.
+ */
+
+#define PS_ERRORNAME_DOMAIN "psModule.src."
+
+//~Start #define PS_ERRORTEXT_$1 "$2"
+#define PS_ERRORTEXT_pmMaskBadPixels_NULL_MASK_IMAGE "Null not allowed for mask image."
+#define PS_ERRORTEXT_pmMaskBadPixels_NULL_INPUT_IMAGE "Null not allowed for input image."
+#define PS_ERRORTEXT_pmMaskBadPixels_SIZE_INPUT_IMAGE "Input image size exceeds that of mask image: (%d, %d) vs (%d, %d)"
+#define PS_ERRORTEXT_pmMaskBadPixels_SIZE_MASK_IMAGE "Input image mask size exceeds that of mask image: (%d, %d) vs (%d, %d)"
+#define PS_ERRORTEXT_pmMaskBadPixels_OFFSET_MASK_IMAGE "Total offset >= mask image: (%d, %d) vs (%d, %d)"
+#define PS_ERRORTEXT_pmMaskBadPixels_OFFSET_INPUT_IMAGE "Total offset >= input image: (%d, %d) vs (%d, %d)"
+#define PS_ERRORTEXT_pmMaskBadPixels_OFFSET_INPUT_IMAGE_MASK "Total offset >= input image mask: (%d, %d) vs (%d, %d)"
+#define PS_ERRORTEXT_pmMaskBadPixels_TYPE_INPUT_IMAGE "Complex types not allowed for input image. Type: %d"
+#define PS_ERRORTEXT_pmMaskBadPixels_TYPE_MASK_IMAGE "Mask must be PS_TYPE_MASK type. Type: %d"
+#define PS_ERRORTEXT_pmMaskBadPixels_TYPE_MISMATCH "Input and flat image types differ: (%d vs %d)"
+#define PS_ERRORTEXT_pmMaskBadPixels_TYPE_UNSUPPORTED "Unsupported image datatype. Type: %d"
+//~End
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/detrend/pmNonLinear.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmNonLinear.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmNonLinear.c	(revision 9533)
@@ -0,0 +1,129 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/** @file  pmNonLinear.c
+ *
+ *  Provides polynomial or table lookup non-linearity corrections to readouts.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-08-29 21:39:44 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ *  XXX: The SDR is silent about image types.  Only F32 was implemented.
+ *
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <pslib.h>
+
+#include "pmNonLinear.h"
+
+/******************************************************************************
+pmNonLinearityLookup(): This routine will take an pmReadout image as input
+and a 1-D polynomial.  For each pixel in the input image, the polynomial will
+be evaluated at that pixels value, and the image pixel will then be set to
+that value.
+ *****************************************************************************/
+
+pmReadout *pmNonLinearityPolynomial(pmReadout *inputReadout,
+                                    const psPolynomial1D *input1DPoly)
+{
+    PS_ASSERT_PTR_NON_NULL(inputReadout, NULL);
+    PS_ASSERT_PTR_NON_NULL(inputReadout->image, NULL);
+    PS_ASSERT_IMAGE_TYPE(inputReadout->image, PS_TYPE_F32, NULL);
+    PS_ASSERT_PTR_NON_NULL(input1DPoly, NULL);
+
+    psS32 i;
+    psS32 j;
+
+    for (i=0;i<inputReadout->image->numRows;i++) {
+        for (j=0;j<inputReadout->image->numCols;j++) {
+            inputReadout->image->data.F32[i][j] = psPolynomial1DEval(input1DPoly, inputReadout->image->data.F32[i][j]);
+        }
+    }
+    return(inputReadout);
+}
+
+
+/******************************************************************************
+pmNonLinearityLookup(): This routine will take an pmReadout image as input
+and two input vectors, which constitute a lookup table.  For each pixel in
+the input image, that pixels value will be determined in the input vector
+inFluxe, and the corresponding value in outFlux.  The image pixel will then
+be set to the value from outFlux.
+ *****************************************************************************/
+pmReadout *pmNonLinearityLookup(pmReadout *inputReadout,
+                                const psVector *inFlux,
+                                const psVector *outFlux)
+{
+    PS_ASSERT_PTR_NON_NULL(inputReadout,NULL);
+    PS_ASSERT_PTR_NON_NULL(inputReadout->image,NULL);
+    PS_ASSERT_IMAGE_TYPE(inputReadout->image, PS_TYPE_F32, NULL);
+    PS_ASSERT_PTR_NON_NULL(inFlux,NULL);
+    if (inFlux->n < 2) {
+        psError(PS_ERR_UNKNOWN,true, "pmNonLinearityLookup(): input vector less than 2 elements.  Returning inputReadout image.");
+        return(inputReadout);
+    }
+    PS_ASSERT_PTR_NON_NULL(outFlux,NULL);
+    psS32 tableSize = inFlux->n;
+    if (inFlux->n != outFlux->n) {
+        tableSize = PS_MIN(inFlux->n, outFlux->n);
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: pmNonLinear.c: pmNonLinearityLookup(): input vectors have different sizes (%ld, %ld)\n", inFlux->n, outFlux->n);
+    }
+    PS_ASSERT_VECTOR_TYPE(inFlux, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_TYPE(outFlux, PS_TYPE_F32, NULL);
+
+    psS32 i;
+    psS32 j;
+    psS32 binNum;
+    psScalar x;
+    psS32 numPixels = 0;
+    psF32 slope;
+
+    x.type.type = PS_TYPE_F32;
+    for (i=0;i<inputReadout->image->numRows;i++) {
+        for (j=0;j<inputReadout->image->numCols;j++) {
+            x.data.F32 = inputReadout->image->data.F32[i][j];
+            binNum = p_psVectorBinDisect((psVector *)inFlux, &x);
+
+            if (binNum == -2) {
+                // We get here if x is below the table lookup range.
+                inputReadout->image->data.F32[i][j] = outFlux->data.F32[0];
+                numPixels++;
+
+            } else if (binNum == -1) {
+                // We get here if x is above the table lookup range.
+                inputReadout->image->data.F32[i][j] = outFlux->data.F32[tableSize-1];
+                numPixels++;
+
+            } else if (binNum < -2) {
+                // We get here if there was some other problem.
+                psError(PS_ERR_UNKNOWN,true, "pmNonLinearityLookup(): Could not perform p_psVectorBinDisect().  Returning inputReadout image.");
+                return(inputReadout);
+                numPixels++;
+            } else {
+                // Perform linear interpolation.
+                slope = (outFlux->data.F32[binNum+1] - outFlux->data.F32[binNum]) /
+                        (inFlux->data.F32[binNum+1]  - inFlux->data.F32[binNum]);
+                inputReadout->image->data.F32[i][j] = outFlux->data.F32[binNum] +
+                                                      ((x.data.F32 - inFlux->data.F32[binNum]) * slope);
+            }
+        }
+    }
+    if (numPixels > 0) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: pmNonLinear.c: pmNonLinearityLookup(): %d pixels outside table.", numPixels);
+    }
+    return(inputReadout);
+}
Index: /tags/RHL_v1_1/psModules/src/detrend/pmNonLinear.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmNonLinear.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmNonLinear.h	(revision 9533)
@@ -0,0 +1,32 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/** @file  pmNonLinear.h
+ *
+ *  Provides polynomial or table lookup non-linearity corrections to readouts.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:01:05 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#if !defined(PM_NON_LINEAR_H)
+#define PM_NON_LINEAR_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+pmReadout *pmNonLinearityPolynomial(pmReadout *in,
+                                    const psPolynomial1D *coeff);
+
+pmReadout *pmNonLinearityLookup(pmReadout *in,
+                                const psVector *inFlux,
+                                const psVector *outFlux);
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/detrend/pmShutterCorrection.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmShutterCorrection.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmShutterCorrection.c	(revision 9533)
@@ -0,0 +1,623 @@
+/*
+ * measure shutter correction:
+ 
+ * input  : collection of shutter correction exposures (pre-processed)
+ * output : a shutter correction image
+ 
+ * the measurement could be performed on any focal-plane unit at a time. for
+ * GPC, the obvious scale is to measure the effect on the entire focal plane at
+ * once, with a single reference point in the field.  this is a little more
+ * complex than just measuring the effect for a single 2D image array.  the
+ * reference point and the detailed analysis points need to be defined for the
+ * entire hierarchy rather than just as coordinate pairs or regions.  a
+ * pmFPAview would be a natural element with which to define these points, but
+ * at the moment, the pmFPAview structure defines a band in the CCD, not a
+ * coordinate.  An option is to instead specify the reference locations as a
+ * pmFPAview coupled with a psRegion, though we need to be careful not to
+ * over-specify the pixels (ie, conflict between pmFPAview and psRegion).
+ 
+ * at each point in an image with exposure time T, we measure f(k;T) = F(k;T) /
+ * F(0;T) where k is the coordinate of the point of interest, 0 is the reference
+ * coordinate, and F(k;T) is the measured number of counts at the point of
+ * interest in this image.  given a collection of f(k;T) values, we need to
+ * determine the model f(k;T) = A(k) (T + dTk) / (T + dTo) where dTk is the shutter
+ * error at the given position, dTo is the shutter error at the reference
+ * position, and A(k) is the scaling factor for the given position.
+ 
+ - load the configuration information
+ - load the input file information, build pmFPAfile's for all inputs
+ 
+ - determine the reference point and detailed analysis regions from the config information
+ - for each image
+ -- measure the reference point counts
+ - for each analysis region:
+ -- measure shutter parameters (dTo, dTk, A):
+ --- for each image:
+ ---- measure counts at the region
+ ---- divide by the reference counts
+ --- linear extrapolation to find f(inf) = A(k)
+ --- linear extrapolation to find f(0) = A(k) dTk / dTo
+ --- linear interpolation to find coordinate where f(dTo) = A (1 + dTk/dTo) / 2
+ --- non-linear fit of T, f(T) to f(k;T) = A(k) (T + dTk) / (T + dTo)
+ - use the collection of dTo values to choose a best value for dTo (median)
+ - for each image pixel
+ -- divide by the reference counts
+ -- generate the vectors T, f(T)
+ -- linear fit of T, f(T) to f(k;T) = A(k) (T + dTk) / (T + dTo) using dTo above
+ -- save dTk, A(k) in output image pixels
+ -- apply dTk, A(k) to measure residual images
+ -- generate residual FITS/JPEG images
+ 
+*/
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <strings.h>
+#include <pslib.h>
+
+#include "pmFPA.h"
+#include "pmShutterCorrection.h"
+#include "psVectorBracket.h"
+
+static void pmShutterCorrectionFree(pmShutterCorrection *pars)
+{
+    // Nothing to free
+    return;
+}
+
+pmShutterCorrection *pmShutterCorrectionAlloc()
+{
+    pmShutterCorrection *corr = (pmShutterCorrection*)psAlloc(sizeof(pmShutterCorrection));
+    psMemSetDeallocator(corr, (psFreeFunc)pmShutterCorrectionFree);
+
+    corr->scale  = 0.0;
+    corr->offset = 0.0;
+    corr->offref = 0.0;
+
+    return corr;
+}
+
+pmShutterCorrection *pmShutterCorrectionGuess(const psVector *exptime, const psVector *counts)
+{
+    // NOTE: vectors must be sorted on input.  It is expensive to sort or check this here, but
+    // it is easy to arrange by sorting the images before generating these vectors.
+
+    PS_ASSERT_VECTOR_NON_NULL(exptime, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(counts, NULL);
+    PS_ASSERT_VECTOR_TYPE(exptime, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_TYPE(counts, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(exptime, counts, NULL);
+    if (exptime->n <= 2) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                "Require more than 2 exposures to guess shutter correction.\n");
+        return NULL;
+    }
+
+    long N = exptime->n;                // Number of exposures
+
+    // use interpolation to guess shutter correction parameters given a set of exposures times and normalized
+    // counts (divided by the reference counts for each image)
+
+    pmShutterCorrection *corr = pmShutterCorrectionAlloc(); // Shutter correction, to be returned
+    psPolynomial1D *line = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 1); // Straight line, for extrapolation
+
+    // choose the highest exptime point as the guess for the scale:
+    // XXX we could examine the top 2 or 3 values and decide if we
+    // extended exptime enough or median clip.
+    corr->scale = counts->data.F32[N-1];
+
+    // fit a line to the lowest three points and extrapolate to 0.0
+    psVector *tmpX = psVectorAlloc(2, PS_TYPE_F32);
+    psVector *tmpY = psVectorAlloc(2, PS_TYPE_F32);
+    tmpX->n = tmpY->n = 2;
+
+    long index;
+    for (index = 0; !isfinite(exptime->data.F32[index]) && index < N - 1; index++)
+        ; // Iterate only
+    if (index == N - 1) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Not enough good values to guess shutter correction.\n");
+        goto GUESS_ERROR;
+    }
+    tmpX->data.F32[0] = exptime->data.F32[index];
+    tmpY->data.F32[0] = counts->data.F32[index];
+
+    for (index++;
+            (!isfinite(exptime->data.F32[index]) || exptime->data.F32[index] == exptime->data.F32[0]) &&
+            index < N; index++)
+        ; // Iterate only
+    if (index == N) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true,
+                "Exposure times are all identical --- cannot guess shutter correction.\n");
+        goto GUESS_ERROR;
+    }
+    tmpY->data.F32[1] = counts->data.F32[index];
+    tmpX->data.F32[1] = exptime->data.F32[index];
+
+    // fit a line and extrapolate the fit to 0.0
+    if (!psVectorFitPolynomial1D(line, NULL, 0, tmpY, NULL, tmpX)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to fit for the time offset.\n");
+        goto GUESS_ERROR;
+    }
+    float ratio = psPolynomial1DEval(line, 0.0) / corr->scale;
+
+    // XXX we need a sanity check:
+    // if the mean value of the three points is higher than corr->scale,
+    // then the slope should be negative.
+    // if the mean value of the three points is lower than corr->scale,
+    // then the slope should be positive.
+
+    // find two points bracketing the value counts = A (1 + dTk/dTo) / 2 = corr->scale (1 + ratio) / 2
+    float value = corr->scale * (1 + ratio) / 2.0;
+
+    int Np;                             // Index of the value above (positive side)
+    if (ratio < 1.0) {
+        Np = psVectorBracket(counts, value, true);
+    } else {
+        Np = psVectorBracketDescend(counts, value, true);
+    }
+    int Nm = (Np == 0) ? 1 : Np - 1;    // Index of the value below (negative side)
+
+    tmpX->data.F32[0] = counts->data.F32[Nm];
+    tmpX->data.F32[1] = counts->data.F32[Np];
+    tmpY->data.F32[0] = exptime->data.F32[Nm];
+    tmpY->data.F32[1] = exptime->data.F32[Np];
+
+    // fit a line and extrapolate the fit to counts = A (1 + dTk/dTo) : exptime = dTo
+    if (!psVectorFitPolynomial1D (line, NULL, 0, tmpY, NULL, tmpX)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to fit for the reference offset.\n");
+        goto GUESS_ERROR;
+    }
+    corr->offref = psPolynomial1DEval(line, value);
+    corr->offset = ratio * corr->offref;
+
+    psFree(line);
+    psFree(tmpX);
+    psFree(tmpY);
+
+    return corr;
+
+GUESS_ERROR:
+    psFree(tmpX);
+    psFree(tmpY);
+    psFree(line);
+    psFree(corr);
+    return NULL;
+}
+
+// linear fit to the counts and exptime, given a value for offref
+pmShutterCorrection *pmShutterCorrectionLinFit(const psVector *exptime,
+        const psVector *counts,
+        const psVector *cntError,
+        const psVector *mask,
+        float offref,
+        int nIter,
+        float rej,
+        psMaskType maskVal
+                                              )
+{
+    PS_ASSERT_VECTOR_NON_NULL(exptime, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(counts, NULL);
+    PS_ASSERT_VECTOR_TYPE(exptime, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_TYPE(counts, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(exptime, counts, NULL);
+    if (exptime->n <= 2) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                "Require more than 2 exposures to guess shutter correction.\n");
+        return NULL;
+    }
+    if (cntError) {
+        PS_ASSERT_VECTOR_TYPE(cntError, PS_TYPE_F32, NULL);
+        PS_ASSERT_VECTORS_SIZE_EQUAL(counts, cntError, NULL);
+    }
+    PS_ASSERT_FLOAT_LARGER_THAN(offref, 0.0, NULL);
+
+    // this step is identical for all pixels: do it once and save?
+    psVector *x = psVectorAlloc(exptime->n, PS_TYPE_F32);
+    psVector *y = psVectorAlloc(exptime->n, PS_TYPE_F32);
+    x->n = y->n = exptime->n;
+
+    for (long i = 0; i < exptime->n; i++) {
+        // Should be safe (if expensive) to stick NaNs in --- the fitter deals with them
+        float value = 1.0 / (exptime->data.F32[i] + offref);
+        x->data.F32[i] = exptime->data.F32[i] * value;
+        y->data.F32[i] = value;
+    }
+
+    psPolynomial2D *line = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, 1, 1);
+
+    // mask out the terms we will not fit
+    line->mask[0][0] = 1;
+    line->mask[1][1] = 1;
+    line->coeff[0][0] = 0;
+    line->coeff[1][1] = 0;
+
+    psStats *stats = psStatsAlloc(0);
+    stats->clipSigma = rej;
+    stats->clipIter = nIter;
+
+    if (!psVectorClipFitPolynomial2D(line, stats, mask, maskVal, counts, cntError, x, y)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to fit shutter correction.\n");
+        psFree(stats);
+        psFree(x);
+        psFree(y);
+        psFree(line);
+        return NULL;
+    }
+    psFree(stats);
+
+    pmShutterCorrection *corr = pmShutterCorrectionAlloc();
+    corr->offref = offref;
+    corr->scale  = line->coeff[1][0];
+    corr->offset = line->coeff[0][1] / line->coeff[1][0];
+
+    psFree(x);
+    psFree(y);
+    psFree(line);
+
+    return corr;
+}
+
+static psF32 pmShutterCorrectionModel(psVector *deriv,
+                                      const psVector *params,
+                                      const psVector *x)
+{
+    // This is in a tight loop, so we won't assert here.
+
+    psF32 A = params->data.F32[0];
+    psF32 p = x->data.F32[0] + params->data.F32[1];
+    psF32 q = 1.0 / (x->data.F32[0] + params->data.F32[2]);
+    psF32 f = A * p * q;
+
+    if (deriv) {
+        deriv->data.F32[0] = p * q;
+        deriv->data.F32[1] = A * q;
+        deriv->data.F32[2] = - f * q;
+    }
+    return f;
+}
+
+// non-linear fit to the counts and exptime, given a guess for the three parameters
+pmShutterCorrection *pmShutterCorrectionFullFit(const psVector *exptime,
+        const psVector *counts,
+        const psVector *cntError,
+        const pmShutterCorrection *guess
+                                               )
+{
+    PS_ASSERT_VECTOR_NON_NULL(exptime, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(counts, NULL);
+    PS_ASSERT_VECTOR_TYPE(exptime, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_TYPE(counts, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(exptime, counts, NULL);
+    if (exptime->n <= 2) {
+        psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                "Require more than 2 exposures to guess shutter correction.\n");
+        return NULL;
+    }
+    if (cntError) {
+        PS_ASSERT_VECTOR_TYPE(cntError, PS_TYPE_F32, NULL);
+        PS_ASSERT_VECTORS_SIZE_EQUAL(counts, cntError, NULL);
+    }
+    PS_ASSERT_PTR_NON_NULL(guess, NULL);
+
+    psMinimization *minInfo = psMinimizationAlloc(15, 0.1); // Minimization information
+
+    psVector *params = psVectorAlloc (3, PS_TYPE_F32); // Fitting parameters
+    params->data.F32[0] = guess->scale;
+    params->data.F32[1] = guess->offset;
+    params->data.F32[2] = guess->offref;
+    params->n = 3;
+
+    // XXX for the moment, don't set any constraints
+    // psMinConstrain *constrain = psMinConstrainAlloc();
+    // constrain->paramDelta = psVectorAlloc (3, PS_TYPE_F32);
+    // constrain->paramMin   = psVectorAlloc (3, PS_TYPE_F32);
+    // constrain->paramMax   = psVectorAlloc (3, PS_TYPE_F32);
+    // constrain->paramMask  = NULL;
+    psMinConstrain *constrain = NULL;   // Constraints on the minimization
+
+    // XXX ignore covariance matrix for the moment
+    // psImage *covar = psImageAlloc (params->n, params->n, PS_TYPE_F64);
+    psImage *covar = NULL;              // Covariance matrix
+
+    // construct the coordinate and value entries (y is counts)
+    psArray *x = psArrayAlloc(exptime->n); // Coordinates
+    x->n = exptime->n;
+
+    for (long i = 0; i < exptime->n; i++) {
+        psVector *coord = psVectorAlloc(1, PS_TYPE_F32);
+        coord->n = 1;
+        coord->data.F32[0] = exptime->data.F32[i];
+        x->data[i] = coord;
+    }
+
+    if (!psMinimizeLMChi2(minInfo, covar, params, constrain, x, counts, cntError, pmShutterCorrectionModel)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to fit for shutter correction.\n");
+        psFree(x);
+        psFree(minInfo);
+        psFree(params);
+        return NULL;
+    }
+
+    pmShutterCorrection *corr = pmShutterCorrectionAlloc(); // Shutter correction
+    corr->scale  = params->data.F32[0];
+    corr->offset = params->data.F32[1];
+    corr->offref = params->data.F32[2];
+
+    psFree(minInfo);
+    psFree(params);
+    psFree(x);
+
+    return corr;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#define MEASURE_SAMPLES 4
+
+psImage *pmShutterCorrectionMeasure(const psVector *exptimes, // Exposure times
+                                    const psArray *images, // Input images
+                                    const psArray *weights, // Weight images
+                                    const psArray *masks, // Mask images
+                                    unsigned int size, // Size of samples
+                                    psStatsOptions meanStat, // Statistic to use for mean
+                                    psStatsOptions stdevStat, // Statistic to use for stdev
+                                    int nIter, // Number of iterations per pixel
+                                    float rej, // Rejection limit
+                                    psMaskType maskVal // Mask value
+                                   )
+{
+    PS_ASSERT_VECTOR_NON_NULL(exptimes, NULL);
+    PS_ASSERT_VECTOR_TYPE(exptimes, PS_TYPE_F32, NULL);
+    PS_ASSERT_ARRAY_NON_NULL(images, NULL);
+    if (masks) {
+        // XXX ASSERT not defined
+        // PS_ASSERT_ARRAYS_SIZE_EQUAL(images, masks, NULL);
+    }
+    if (weights) {
+        // XXX ASSERT not defined
+        // PS_ASSERT_ARRAYS_SIZE_EQUAL(images, weights, NULL);
+    }
+    long num = images->n;               // Number of images
+    PS_ASSERT_VECTOR_SIZE(exptimes, num, NULL);
+    PS_ASSERT_INT_POSITIVE(nIter, NULL);
+    PS_ASSERT_FLOAT_LARGER_THAN(rej, 0.0, NULL);
+
+    // Check input sizes, generate first-pass statistics
+    psRegion refRegion;                 // Reference region
+    psVector *refs = psVectorAlloc(num, PS_TYPE_F32); // Reference measurements
+    refs->n = num;
+    psVectorInit(refs, 0);
+    psArray *regions = psArrayAlloc(MEASURE_SAMPLES); // Array of sample regions, made on each image
+    regions->n = MEASURE_SAMPLES;
+    psImage *samplesMean = psImageAlloc(num, MEASURE_SAMPLES, PS_TYPE_F32); // Measurements for each file
+    psImage *samplesStdev = psImageAlloc(num, MEASURE_SAMPLES, PS_TYPE_F32); // Errors for each file
+    psStats *stats = psStatsAlloc(meanStat | stdevStat);
+    int numRows = 0, numCols = 0; // Size of images
+    for (long i = 0; i < images->n; i++) {
+        psImage *image = images->data[i]; // Image of interest
+        if (!image) {
+            continue;
+        }
+        if (image->type.type != PS_TYPE_F32) {
+            psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Bad type for image: %x\n", image->type.type);
+            goto MEASURE_ERROR;
+        }
+        if (numRows == 0 || numCols == 0) {
+            numRows = image->numRows;
+            numCols = image->numCols;
+            // Set up the sample regions
+            refRegion = psRegionForSquare(0.5 * numCols, 0.5 * numRows, size);
+            for (int j = 0; j < MEASURE_SAMPLES; j++) {
+                int x = (j % 2) ? size : image->numCols - size;
+                int y = (j > 1) ? size : image->numRows - size;
+                psRegion region = psRegionForSquare(x, y, size);
+                region = psRegionForImage(image, region);
+                regions->data[j] = psRegionAlloc(region.x0, region.x1, region.y0, region.y1);
+            }
+        } else if (numRows != image->numRows || numCols != image->numCols) {
+            psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                    "Image sizes don't match: %dx%d vs %dx%d\n", image->numCols, image->numRows,
+                    numCols, numRows);
+            goto MEASURE_ERROR;
+        }
+        if (masks) {
+            psImage *mask = masks->data[i];
+            if (mask) {
+                if (mask->type.type != PS_TYPE_U8) {
+                    psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Bad type for mask: %x\n", mask->type.type);
+                    goto MEASURE_ERROR;
+                }
+                if (mask->numRows != numRows || mask->numCols != numCols) {
+                    psError(PS_ERR_BAD_PARAMETER_SIZE, true,
+                            "Mask sizes don't match: %dx%d vs %dx%d\n", mask->numCols, mask->numRows,
+                            numCols, numRows);
+                    goto MEASURE_ERROR;
+                }
+            }
+        }
+
+        // Measure statistics
+        if (!psImageStats(stats, image, masks->data[i], maskVal)) {
+            psWarning("Unable to measure reference statistics.\n");
+        }
+        refs->data.F32[i] = psStatsGetValue(stats, meanStat);
+        psTrace("psModules.detrend", 3, "Reference value for image %ld = %f\n", i, refs->data.F32[i]);
+        if (refs->data.F32[i] <= 0.0) {
+            psError(PS_ERR_UNKNOWN, true, "Measured non-positive reference value.\n");
+            goto MEASURE_ERROR;
+        }
+        refs->data.F32[i] = 1.0 / refs->data.F32[i];
+        for (int j = 0; j < MEASURE_SAMPLES; j++) {
+            psRegion *region = regions->data[j]; // Region of interest
+            psImage *subImage = psImageSubset(image, *region); // Sub-image
+            if (!psImageStats(stats, subImage, masks->data[i], maskVal)) {
+                psString regionString = psRegionToString(*region);
+                psWarning("Unable to measure sample statistics at %s in image %ld.\n",
+                          regionString, i);
+                psFree(regionString);
+            }
+            psFree(subImage);
+            samplesMean->data.F32[j][i] = psStatsGetValue(stats, meanStat) * refs->data.F32[i];
+            samplesStdev->data.F32[j][i] = psStatsGetValue(stats, stdevStat) * refs->data.F32[i];
+            psTrace("psModules.detrend", 5, "Image %ld, sample %d: %f +/- %f\n", i, j,
+                    samplesMean->data.F32[j][i], samplesStdev->data.F32[j][i]);
+        }
+    }
+    psFree(regions);
+    psFree(stats);
+
+    float meanRef = 0.0;                // Mean reference offset
+    int numGood = 0;                    // Number of good measurements
+    psVector *counts = psVectorAlloc(num, PS_TYPE_F32); // Mean for each image
+    psVector *errors = psVectorAlloc(num, PS_TYPE_F32); // Stdev for each image
+    for (int i = 0; i < MEASURE_SAMPLES; i++) {
+        counts = psImageRow(counts, samplesMean, i);
+        errors = psImageRow(errors, samplesStdev, i);
+        pmShutterCorrection *guess = pmShutterCorrectionGuess(exptimes, counts); // Guess at correction
+        pmShutterCorrection *corr = pmShutterCorrectionFullFit(exptimes, counts, errors, guess); // Correct'n
+        if (!corr) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to measure shutter reference correction.\n");
+            psFree(guess);
+            psFree(counts);
+            psFree(errors);
+            goto MEASURE_ERROR;
+        }
+        psTrace("psModules.detrend", 5, "Sample reference value: %f\n", corr->offref);
+        if (isfinite(corr->offref)) {
+            meanRef += corr->offref;
+            numGood++;
+        }
+        psFree(corr);
+        psFree(guess);
+    }
+    psFree(samplesMean);
+    psFree(samplesStdev);
+
+    if (numGood == 0) {
+        psError(PS_ERR_UNKNOWN, true, "Unable to measure mean reference offset.\n");
+        psFree(counts);
+        psFree(errors);
+        goto MEASURE_ERROR;
+    }
+    meanRef /= (float)numGood;
+    psTrace("psModules.detrend", 3, "Mean reference value: %f\n", meanRef);
+
+    // Check the weights
+    if (weights && nIter > 1) {
+        for (int i = 0; i < weights->n && nIter > 1; i++) {
+            psImage *weight = weights->data[i]; // Weight image
+            if (!weight) {
+                // We don't have weights, so no realistic errors: turn off iteration
+                if (nIter > 0) {
+                    psWarning("Not all images have weights --- turning iteration off.\n");
+                }
+                nIter = 1;
+            }
+        }
+    }
+
+    psImage *shutter = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Shutter correction image
+    psImage *pattern = psImageAlloc(numCols, numRows, PS_TYPE_F32); // Illumination pattern
+    psVector *mask = psVectorAlloc(num, PS_TYPE_U8); // Mask for each image
+    mask->n = num;
+    psVectorInit(mask, 0);
+    psTrace("psModules.detrend", 2, "Performing linear fit on individual pixels...\n");
+    for (int y = 0; y < numRows; y++) {
+        for (int x = 0; x < numCols; x++) {
+            for (int i = 0; i < num; i++) {
+                psImage *image = images->data[i]; // Image of interest
+                counts->data.F32[i] = image->data.F32[y][x] * refs->data.F32[i];
+                psImage *maskImage;     // Mask image
+                if (masks && (maskImage = masks->data[i])) {
+                    mask->data.U8[i] = maskImage->data.U8[y][x];
+                }
+                psImage *weight;        // Weight image
+                if (weights && (weight = weights->data[i])) {
+                    errors->data.F32[i] = sqrtf(weight->data.F32[y][x]) * refs->data.F32[i];
+                } else {
+                    errors->data.F32[i] = sqrtf(image->data.F32[y][x]) * refs->data.F32[i];
+                }
+            }
+
+            pmShutterCorrection *corr = pmShutterCorrectionLinFit(exptimes, counts, errors, mask, meanRef,
+                                        nIter, rej, maskVal);
+            shutter->data.F32[y][x] = corr->offset;
+            pattern->data.F32[y][x] = corr->scale;
+            psFree(corr);
+        }
+    }
+    psFree(mask);
+    psFree(counts);
+    psFree(errors);
+    psFree(refs);
+
+    psFits *fits = psFitsOpen("pattern.fits", "w");
+    psFitsWriteImage(fits, NULL, pattern, 0, NULL);
+    psFitsClose(fits);
+    psFree(pattern);
+
+    return shutter;
+
+
+MEASURE_ERROR:
+    // Clean up after error
+    psFree(refs);
+    psFree(regions);
+    psFree(stats);
+    psFree(samplesMean);
+    psFree(samplesStdev);
+    return NULL;
+}
+
+
+bool pmShutterCorrectionApply(pmReadout *readout, // Readout to which to apply shutter correction
+                              const pmReadout *shutter // Shutter correction readout
+                             )
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(shutter, false);
+    PS_ASSERT_IMAGE_NON_NULL(readout->image, false);
+    PS_ASSERT_IMAGE_NON_NULL(shutter->image, false);
+    PS_ASSERT_IMAGE_TYPE(readout->image, PS_TYPE_F32, false);
+    PS_ASSERT_IMAGE_TYPE(shutter->image, PS_TYPE_F32, false);
+
+    psRegion region = psRegionSet(readout->col0, readout->col0 + readout->image->numCols,
+                                  readout->row0, readout->row0 + readout->image->numRows); // Detector region
+
+    pmCell *cell = readout->parent;     // Parent cell
+    if (!cell) {
+        psError(PS_ERR_BAD_PARAMETER_NULL, true,
+                "Parent cell is NULL --- unable to determine exposure time.\n");
+        return false;
+    }
+    float exptime = psMetadataLookupF32(NULL, cell->concepts, "CELL.EXPOSURE"); // Exposure time
+    if (!isfinite(exptime) || exptime < 0.0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Bad exposure time: %f.\n", exptime);
+        return false;
+    }
+
+    psImage *shutterImage = psImageSubset(shutter->image, region); // Subimage with shutter
+    if (!shutterImage) {
+        psString regionString = psRegionToString(region);
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Size mismatch: %s vs %dx%d\n",
+                regionString, shutter->image->numCols, shutter->image->numRows);
+        psFree(regionString);
+        psFree(shutterImage);
+        return false;
+    }
+    psImage *image = readout->image;    // Image to correct
+    for (int y = 0; y < image->numRows; y++) {
+        for (int x = 0; x < image->numCols; x++) {
+            image->data.F32[y][x] *= exptime / (exptime + shutterImage->data.F32[y][x]);
+        }
+    }
+
+    psFree(shutterImage);
+
+    return true;
+}
Index: /tags/RHL_v1_1/psModules/src/detrend/pmShutterCorrection.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmShutterCorrection.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmShutterCorrection.h	(revision 9533)
@@ -0,0 +1,116 @@
+/** @file  pmShutterCorrection.h
+ *
+ *  @brief Functions to build and apply a shutter exposure-time correction.
+ *
+ *  A mechanical shutter may not yield uniform exposure times as a function of
+ *  position on the detector.  The typical error consists of a constant
+ *  exposure-time offset relative to the requested value, ie exposure time is
+ *  T_o + dT(x,y).  The exposure error, dT, may be measured with the following
+ *  scheme.  Obtain a set of exposures with different exposures times taken of
+ *  the same flat-field source; the source must be spatially stable between the
+ *  exposures, but need not have a stable amplitude.  For an illuminating flux
+ *  of intensity F(x,y) = F_o f(x,y), the signal recorded by any pixel in the
+ *  detector is given by: S(t,x,y) = F_o(t) f(x,y) (T_o + dT(x,y)) where F_o is
+ *  the F_o(t) is the (variable) overall intensity of the illuminating source
+ *  and f(x,y) is the spatial illumination patter times the flat-field response.
+ *  Choose a reference location in the image (eg, the detector center) and
+ *  divide by the value of that region (ie, mean or median):
+ 
+ *  s(t,x,y) = S(t,x,y) / S(t,0,0)
+ *  s(t,x,y) = F_o(t) f(x,y) (T_o + dT(x,y)) / F_o(t) f(0,0) (T_o + dT(0,0))
+ *  s(t,x,y) = f(x,y) (T_o + dT(x,y)) / f(0,0) (T_o + dT(0,0))
+ 
+ * we can absorb the term f(0,0) into f(x,y) as we have no motivation for the
+ * scale of f(x,y).  For any single pixel, over the set of exposures, we thus
+ * need to solve for dT(x,y), dT(0,0), and f'(x,y) in the equation:
+ * s(t,x,y) = f'(x,y) (T_o + dT(x,y)) / (T_o + dT(0,0))
+ 
+ * we avoid directly fitting these values as the process would be a non-linear
+ * least-squares problem for every pixel in the image, and thus very time
+ * consuming.  There are linear options which may be used instead.
+ * First, as T_o goes to a large value, s() approaches the value of f'(x,y).
+ * Next, as T_o goes to a very small value, s() approaches the value of
+ * f'(x,y)*dT(x,y)/dT(0,0).  Finally, when s() has the value of
+ * f'(x,y)*(1 + dT(x,y)/dT(0,0))/2, T_o has the value of dT(0,0).  with data
+ * points covering a reasonable dynamic range, we can solve for these three
+ * values by interpolation and/or extrapolation.
+ 
+ * To take the strategy one step further, we could use the above recipe to
+ * obtain a guess for the three parameters and then apply non-linear fitting to
+ * solve more accurately for the parameters.  If we limit this operation to a
+ * handful of positions in the image (user defined, but the obvious choice would
+ * be positions near the center, edges, and corners), then we may determine a
+ * good value for dT(0,0).  Since there is only one dT(0,0) for the image, we
+ * can apply the resulting measurement to the rest of the pixels in the image.
+ * If dT(0,0) is not a free parameter, then the fitting process is linear in
+ * terms of dT(x,y) and f'(x,y)
+ 
+ *  @author Eugene Magnier, IfA
+ *
+ *  @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-10-06 03:33:49 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#ifndef PM_SHUTTER_CORRECTION_H
+#define PM_SHUTTER_CORRECTION_H
+
+#include "pslib.h"
+
+// Shutter correction parameters, applicable for a single pixel
+typedef struct
+{
+    double scale;                       // The normalisation for an exposure, A(k)
+    double offset;                      // The time offset, dTk
+    double offref;                      // The reference time offset, dTo
+}
+pmShutterCorrection;
+
+// Allocator
+pmShutterCorrection *pmShutterCorrectionAlloc();
+
+// Guess a shutter correction, based on plot of counts vs exposure time.
+// Used before doing the full non-linear fit, to get parameters close to the true.
+// Assumes exptime vector is sorted (ascending order; longest is last) prior to input.
+pmShutterCorrection *pmShutterCorrectionGuess(const psVector *exptime, // Exposure times for each exposure
+        const psVector *counts // Counts for each exposure
+                                             );
+
+// Generate shutter correction based on a linear fit
+pmShutterCorrection *pmShutterCorrectionLinFit(const psVector *exptime, // Exposure times for each exposure
+        const psVector *counts, // Counts for each exposure
+        const psVector *cntError, // Error in the counts, for each exp.
+        const psVector *mask, // Mask for each exposure
+        float offref, // Reference time offset
+        int nIter, // Number of iterations
+        float rej, // Rejection threshold (sigma)
+        psMaskType maskVal // Mask value
+                                              );
+
+// Generate shutter correction based on a full non-linear fit
+pmShutterCorrection *pmShutterCorrectionFullFit(const psVector *exptime, // Exposure times for each exposure
+        const psVector *counts, // Counts for each exposure
+        const psVector *cntError, // Error in the counts, for each exp
+        const pmShutterCorrection *guess // Initial guess
+                                               );
+
+// Measure a shutter correction image from an array of images
+psImage *pmShutterCorrectionMeasure(const psVector *exptimes, // Exposure times
+                                    const psArray *images, // Input images
+                                    const psArray *weights, // Weight images
+                                    const psArray *masks, // Mask images
+                                    unsigned int size, // Size of samples
+                                    psStatsOptions meanStat, // Statistic to use for mean
+                                    psStatsOptions stdevStat, // Statistic to use for stdev
+                                    int nIter, // Number of iterations
+                                    float rej, // Rejection threshold (sigma)
+                                    psMaskType maskVal // Mask value
+                                   );
+
+// Apply a shutter correction
+bool pmShutterCorrectionApply(pmReadout *readout, // Readout to which to apply shutter correction
+                              const pmReadout *shutter // Shutter correction readout
+                             );
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/detrend/pmSubtractBias.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmSubtractBias.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmSubtractBias.c	(revision 9533)
@@ -0,0 +1,432 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/** @file  pmSubtractBias.c
+ *
+ *  This file will contain a module which will subtract the detector bias
+ *  in place from an input image.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-08-29 21:39:44 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <assert.h>
+#include <pslib.h>
+#include "pmSubtractBias.h"
+
+#define PM_SUBTRACT_BIAS_POLYNOMIAL_ORDER 2
+#define PM_SUBTRACT_BIAS_SPLINE_ORDER 3
+
+
+#define MAX(a,b) ((a) > (b) ? (a) : (b))
+#define MIN(a,b) ((a) < (b) ? (a) : (b))
+
+
+// XXX: put these in psConstants.h
+void PS_POLY1D_PRINT(psPolynomial1D *poly)
+{
+    printf("-------------- PS_POLY1D_PRINT() --------------\n");
+    printf("poly->nX is %d\n", poly->nX);
+    for (psS32 i = 0 ; i < (1 + poly->nX) ; i++) {
+        printf("poly->coeff[%d] is %f\n", i, poly->coeff[i]);
+    }
+}
+
+void PS_PRINT_SPLINE(psSpline1D *mySpline)
+{
+    printf("-------------- PS_PRINT_SPLINE() --------------\n");
+    printf("mySpline->n is %d\n", mySpline->n);
+    for (psS32 i = 0 ; i < mySpline->n ; i++) {
+        PS_POLY1D_PRINT(mySpline->spline[i]);
+    }
+    PS_VECTOR_PRINT_F32(mySpline->knots);
+}
+
+#define PS_IMAGE_PRINT_F32_HIDEF(NAME) \
+printf("======== printing %s ========\n", #NAME); \
+for (int i = 0 ; i < (NAME)->numRows ; i++) { \
+    for (int j = 0 ; j < (NAME)->numCols ; j++) { \
+        printf("%.5f ", (NAME)->data.F32[i][j]); \
+    } \
+    printf("\n"); \
+}\
+
+
+void overscanOptionsFree(pmOverscanOptions *options)
+{
+    psFree(options->stat);
+    psFree(options->poly);
+    psFree(options->spline);
+}
+
+pmOverscanOptions *pmOverscanOptionsAlloc(bool single, pmFit fitType, unsigned int order, psStats *stat)
+{
+    pmOverscanOptions *opts = psAlloc(sizeof(pmOverscanOptions));
+    psMemSetDeallocator(opts, (psFreeFunc)overscanOptionsFree);
+
+    // Inputs
+    opts->single = single;
+    opts->fitType = fitType;
+    opts->order = order;
+    opts->stat = psMemIncrRefCounter(stat);
+
+    // Outputs
+    opts->poly = NULL;
+    opts->spline = NULL;
+
+    return opts;
+}
+
+
+/******************************************************************************
+psSubtractFrame(): this routine will take as input a readout for the input
+image and a readout for the bias image.  The bias image is subtracted in
+place from the input image.
+*****************************************************************************/
+static bool SubtractFrame(pmReadout *in,// Input readout
+                          const pmReadout *sub, // Readout to be subtracted from input
+                          float scale   // Scale to apply before subtracting
+                         )
+{
+    assert(in);
+    assert(sub);
+
+    psImage *inImage  = in->image;      // The input image
+    psImage *inMask   = in->mask;       // The input mask
+    psImage *subImage = sub->image;     // The image to be subtracted
+    psImage *subMask  = sub->mask;      // The mask for the subtraction image
+
+    int xIpar = psMetadataLookupS32(NULL, in->parent->concepts, "CELL.XPARITY");
+    int xSpar = psMetadataLookupS32(NULL, sub->parent->concepts, "CELL.XPARITY");
+    if (xIpar != xSpar) {
+        psError(PS_ERR_UNKNOWN, true, "images for subtraction do not have the same "
+                "CELL.XPARITY (%d vs %d).\n    pmSubtractBias must be upgraded to handle this situation\n",
+                xIpar, xSpar);
+        return false;
+    }
+
+    int yIpar = psMetadataLookupS32(NULL, in->parent->concepts, "CELL.YPARITY");
+    int ySpar = psMetadataLookupS32(NULL, sub->parent->concepts, "CELL.YPARITY");
+    if (yIpar != ySpar) {
+        psError(PS_ERR_UNKNOWN, true, "images for subtraction do not have the same "
+                "CELL.YPARITY (%d vs %d).\n    pmSubtractBias must be upgraded to handle this situation\n",
+                xIpar, xSpar);
+        return false;
+    }
+
+    // Offsets of the cells
+    int x0in = psMetadataLookupS32(NULL, in->parent->concepts, "CELL.X0");
+    int y0in = psMetadataLookupS32(NULL, in->parent->concepts, "CELL.Y0");
+    int x0sub = psMetadataLookupS32(NULL, sub->parent->concepts, "CELL.X0");
+    int y0sub = psMetadataLookupS32(NULL, sub->parent->concepts, "CELL.Y0");
+
+    if ((inImage->numCols + x0in - x0sub) > subImage->numCols) {
+        psError(PS_ERR_UNKNOWN, true, "Image does not have enough columns for subtraction.\n");
+        return false;
+    }
+    if ((inImage->numRows + y0in - y0sub) > subImage->numRows) {
+        psError(PS_ERR_UNKNOWN, true, "Image does not have enough rows for subtraction.\n");
+        return false;
+    }
+
+    if (scale == 1.0) {
+        for (int i = 0; i < inImage->numRows; i++) {
+            for (int j = 0; j < inImage->numCols; j++) {
+                inImage->data.F32[i][j] -= subImage->data.F32[i+y0in-y0sub][j+x0in-x0sub];
+                if (inMask && subMask) {
+                    inMask->data.U8[i][j] |= subMask->data.U8[i+y0in-y0sub][j+x0in-x0sub];
+                }
+            }
+        }
+    } else {
+        for (int i = 0; i < inImage->numRows; i++) {
+            for (int j = 0; j < inImage->numCols; j++) {
+                inImage->data.F32[i][j] -= subImage->data.F32[i+y0in-y0sub][j+x0in-x0sub] * scale;
+                if (inMask && subMask) {
+                    inMask->data.U8[i][j] |= subMask->data.U8[i+y0in-y0sub][j+x0in-x0sub];
+                }
+            }
+        }
+    }
+
+    return true;
+}
+
+// Produce an overscan vector from an array of pixels
+static psVector *overscanVector(pmOverscanOptions *overscanOpts, // Overscan options
+                                const psArray *pixels, // Array of vectors containing the pixel values
+                                psStats *myStats // Statistic to use in reducing the overscan
+                               )
+{
+    assert(overscanOpts);
+    assert(pixels);
+    assert(myStats);
+
+    psStatsOptions statistic = psStatsSingleOption(myStats->options); // Statistic to use
+    assert(statistic != 0);
+
+    // Reduce the overscans
+    psVector *reduced = psVectorAlloc(pixels->n, PS_TYPE_F32); // Overscan for each row
+    psVector *ordinate = psVectorAlloc(pixels->n, PS_TYPE_F32); // Ordinate
+    psVector *mask = psVectorAlloc(pixels->n, PS_TYPE_U8); // Mask for fitting
+    reduced->n = ordinate->n = mask->n = pixels->n;
+
+    for (int i = 0; i < pixels->n; i++) {
+        psVector *values = pixels->data[i]; // Vector with overscan values
+        if (values->n > 0) {
+            mask->data.U8[i] = 0;
+            ordinate->data.F32[i] = 2.0*(float)i/(float)pixels->n - 1.0; // Scale to [-1,1]
+            psVectorStats(myStats, values, NULL, NULL, 0);
+            reduced->data.F32[i] = psStatsGetValue(myStats, statistic);
+        } else if (overscanOpts->fitType == PM_FIT_NONE) {
+            psError(PS_ERR_UNKNOWN, true, "The overscan is not supplied for all points on the "
+                    "image, and no fit is requested.\n");
+            return NULL;
+        } else {
+            // We'll fit this one out
+            mask->data.U8[i] = 1;
+        }
+    }
+
+    // Fit the overscan, if required
+    switch (overscanOpts->fitType) {
+    case PM_FIT_NONE:
+        // No fitting --- that's easy.
+        break;
+    case PM_FIT_POLY_ORD:
+        if (overscanOpts->poly && (overscanOpts->poly->nX != overscanOpts->order ||
+                                   overscanOpts->poly->type != PS_POLYNOMIAL_ORD)) {
+            psFree(overscanOpts->poly);
+            overscanOpts->poly = NULL;
+        }
+        if (! overscanOpts->poly) {
+            overscanOpts->poly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, overscanOpts->order);
+        }
+        psVectorFitPolynomial1D(overscanOpts->poly, mask, 1, reduced, NULL, ordinate);
+        psFree(reduced);
+        reduced = psPolynomial1DEvalVector(overscanOpts->poly, ordinate);
+        break;
+    case PM_FIT_POLY_CHEBY:
+        if (overscanOpts->poly && (overscanOpts->poly->nX != overscanOpts->order ||
+                                   overscanOpts->poly->type != PS_POLYNOMIAL_CHEB)) {
+            psFree(overscanOpts->poly);
+            overscanOpts->poly = NULL;
+        }
+        if (! overscanOpts->poly) {
+            overscanOpts->poly = psPolynomial1DAlloc(PS_POLYNOMIAL_CHEB, overscanOpts->order);
+        }
+        psVectorFitPolynomial1D(overscanOpts->poly, mask, 1, reduced, NULL, ordinate);
+        psFree(reduced);
+        reduced = psPolynomial1DEvalVector(overscanOpts->poly, ordinate);
+        break;
+    case PM_FIT_SPLINE:
+        // XXX I don't think psSpline1D is up to scratch yet --- it has no mask, and requires an
+        // input spline
+        overscanOpts->spline = psVectorFitSpline1D(reduced, ordinate);
+        psFree(reduced);
+        reduced = psSpline1DEvalVector(overscanOpts->spline, ordinate);
+        break;
+    default:
+        psError(PS_ERR_UNKNOWN, true, "Unknown value for the fitting type: %d\n", overscanOpts->fitType);
+        return NULL;
+        break;
+    }
+
+    psFree(ordinate);
+    psFree(mask);
+
+    return reduced;
+}
+
+
+
+/******************************************************************************
+XXX: The SDRS does not specify type support.  F32 is implemented here.
+ *****************************************************************************/
+bool pmSubtractBias(pmReadout *in, pmOverscanOptions *overscanOpts,
+                    const pmReadout *bias, const pmReadout *dark)
+{
+    psTrace("psModules.detrend", 4,
+            "---- pmSubtractBias() begin ----\n");
+    PS_ASSERT_PTR_NON_NULL(in, NULL);
+    PS_ASSERT_IMAGE_NON_NULL(in->image, NULL);
+    PS_ASSERT_IMAGE_TYPE(in->image, PS_TYPE_F32, NULL);
+    if (bias) {
+        PS_ASSERT_IMAGE_NON_NULL(bias->image, NULL);
+        PS_ASSERT_IMAGE_TYPE(bias->image, PS_TYPE_F32, NULL);
+    }
+    if (dark) {
+        PS_ASSERT_IMAGE_NON_NULL(dark->image, NULL);
+        PS_ASSERT_IMAGE_TYPE(dark->image, PS_TYPE_F32, NULL);
+    }
+
+    psImage *image = in->image;         // The input image
+
+    // Overscan processing
+    if (overscanOpts) {
+        // Check for an unallowable pmFit.
+        if (overscanOpts->fitType != PM_FIT_NONE && overscanOpts->fitType != PM_FIT_POLY_ORD &&
+                overscanOpts->fitType != PM_FIT_POLY_CHEBY && overscanOpts->fitType != PM_FIT_SPLINE) {
+            psError(PS_ERR_UNKNOWN, true, "Invalid fit type (%d).  Returning original image.\n", overscanOpts->fitType);
+            return false;
+        }
+
+        psList *overscans = in->bias; // List of the overscan images
+
+        psStatsOptions statistic = psStatsSingleOption(overscanOpts->stat->options); // Statistic to use
+        if (statistic == 0) {
+            psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Multiple or no statistics options set: %p\n",
+                    overscanOpts->stat);
+            return false;
+        }
+        psStats *stats = psStatsAlloc(statistic); // A new psStats, to avoid clobbering original
+
+        // Reduce all overscan pixels to a single value
+        if (overscanOpts->single) {
+            psVector *pixels = psVectorAlloc(0, PS_TYPE_F32);
+            psListIterator *iter = psListIteratorAlloc(overscans, PS_LIST_HEAD, false); // Iterator
+            psImage *overscan = NULL;   // Overscan image from iterator
+            while ((overscan = psListGetAndIncrement(iter))) {
+                int index = pixels->n;  // Index
+                pixels = psVectorRealloc(pixels, pixels->n + overscan->numRows * overscan->numCols);
+                // XXX Reimplement with memcpy
+                for (int i = 0; i < overscan->numRows; i++) {
+                    for (int j = 0; j < overscan->numCols; j++) {
+                        pixels->data.F32[index++] = overscan->data.F32[i][j];
+                    }
+                }
+
+            }
+            psFree(iter);
+
+            (void)psVectorStats(stats, pixels, NULL, NULL, 0);
+            double reduced = psStatsGetValue(stats, statistic); // Result of statistics
+            (void)psBinaryOp(image, image, "-", psScalarAlloc((float)reduced, PS_TYPE_F32));
+        } else {
+
+            // We do the regular overscan subtraction
+
+            bool readRows = psMetadataLookupBool(NULL, in->parent->concepts, "CELL.READDIR");// Read direction
+
+            if (readRows) {
+                // The read direction is rows
+                psArray *pixels = psArrayAlloc(image->numRows); // Array of vectors containing pixels
+                pixels->n = image->numRows;
+                for (int i = 0; i < pixels->n; i++) {
+                    psVector *values = psVectorAlloc(0, PS_TYPE_F32);
+                    pixels->data[i] = values;
+                }
+
+                // Pull the pixels out into the vectors
+                psListIterator *iter = psListIteratorAlloc(overscans, PS_LIST_HEAD, false); // Iterator
+                psImage *overscan = NULL; // Overscan image from iterator
+                while ((overscan = psListGetAndIncrement(iter))) {
+                    int diff = image->row0 - overscan->row0; // Offset between the two regions
+                    for (int i = MAX(0,diff); i < MIN(image->numRows, overscan->numRows + diff); i++) {
+                        // i is row on overscan
+                        // XXX Reimplement with memcpy
+                        psVector *values = pixels->data[i];
+                        int index = values->n; // Index in the vector
+                        values = psVectorRealloc(values, values->n + overscan->numCols);
+                        for (int j = 0; j < overscan->numCols; j++) {
+                            values->data.F32[index++] = overscan->data.F32[i][j];
+                        }
+                        values->n += overscan->numCols;
+                        pixels->data[i] = values; // Update the pointer in case it's moved
+                    }
+                }
+                psFree(iter);
+
+                // Reduce the overscans
+                psVector *reduced = overscanVector(overscanOpts, pixels, stats);
+                psFree(pixels);
+                if (! reduced) {
+                    return false;
+                }
+
+                // Subtract row by row
+                for (int i = 0; i < image->numRows; i++) {
+                    for (int j = 0; j < image->numCols; j++) {
+                        image->data.F32[i][j] -= reduced->data.F32[i];
+                    }
+                }
+                psFree(reduced);
+
+            } else {
+                // The read direction is columns
+                psArray *pixels = psArrayAlloc(image->numCols); // Array of vectors containing pixels
+                pixels->n = image->numCols;
+                for (int i = 0; i < pixels->n; i++) {
+                    psVector *values = psVectorAlloc(0, PS_TYPE_F32);
+                    pixels->data[i] = values;
+                }
+
+                // Pull the pixels out into the vectors
+                psListIterator *iter = psListIteratorAlloc(overscans, PS_LIST_HEAD, false); // Iterator
+                psImage *overscan = NULL; // Overscan image from iterator
+                while ((overscan = psListGetAndIncrement(iter))) {
+                    int diff = image->col0 - overscan->col0; // Offset between the two regions
+                    for (int i = MAX(0,diff); i < MIN(image->numCols, overscan->numCols + diff); i++) {
+                        // i is column on overscan
+                        // XXX Reimplement with memcpy
+                        psVector *values = pixels->data[i];
+                        int index = values->n; // Index in the vector
+                        values = psVectorRealloc(values, values->n + overscan->numRows);
+                        for (int j = 0; j < overscan->numRows; j++) {
+                            values->data.F32[index++] = overscan->data.F32[i][j];
+                        }
+                        values->n += overscan->numRows;
+                        pixels->data[i] = values; // Update the pointer in case it's moved
+                    }
+                }
+                psFree(iter);
+
+                // Reduce the overscans
+                psVector *reduced = overscanVector(overscanOpts, pixels, stats);
+                psFree(pixels);
+                if (! reduced) {
+                    return false;
+                }
+
+                // Subtract column by column
+                for (int i = 0; i < image->numCols; i++) {
+                    for (int j = 0; j < image->numRows; j++) {
+                        image->data.F32[j][i] -= reduced->data.F32[i];
+                    }
+                }
+                psFree(reduced);
+            }
+        }
+        psFree(stats);
+    } // End of overscan subtraction
+
+    // Bias frame subtraction
+    if (bias) {
+        if (!SubtractFrame(in, bias, 1.0))
+            return false;
+    }
+
+    if (dark) {
+        // Get the scaling
+        float inTime = psMetadataLookupF32(NULL, in->parent->concepts, "CELL.DARKTIME");
+        float darkTime = psMetadataLookupF32(NULL, dark->parent->concepts, "CELL.DARKTIME");
+        if (!SubtractFrame(in, dark, inTime/darkTime))
+            return false;
+    }
+
+    return true;
+}
+
+
Index: /tags/RHL_v1_1/psModules/src/detrend/pmSubtractBias.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmSubtractBias.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmSubtractBias.h	(revision 9533)
@@ -0,0 +1,77 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/** @file  pmSubtractBias.h
+ *
+ *  This file will contain a module which will subtract the detector bias
+ *  in place from an input image.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-06-17 01:50:43 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#if !defined(PM_SUBTRACT_BIAS_H)
+#define PM_SUBTRACT_BIAS_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include<stdio.h>
+#include<math.h>
+#include "pslib.h"
+
+#include "pmFPA.h"
+
+typedef enum {
+    PM_OVERSCAN_NONE,                         ///< No overscan subtraction
+    PM_OVERSCAN_EDGE,                         ///< Subtract the statistic of pixels along the to-be-determined readout direction
+    PM_OVERSCAN_ROWS,                         ///< Subtract rows
+    PM_OVERSCAN_COLUMNS,                      ///< Subtract columns
+    PM_OVERSCAN_ALL                           ///< Subtract the statistic of all pixels in overscan region
+} pmOverscanAxis;
+
+typedef enum {
+    PM_FIT_NONE,                        ///< No fit
+    PM_FIT_POLY_ORD,                    ///< Fit ordinary polynomial
+    PM_FIT_POLY_CHEBY,                  ///< Fit Chebyshev polynomial
+    PM_FIT_SPLINE                       ///< Fit cubic splines
+} pmFit;
+
+typedef struct
+{
+    // Inputs
+    bool single;                // Reduce all overscan regions to a single value?
+    pmFit fitType;              // Type of fit to overscan
+    unsigned int order;         // Order of polynomial, or number of spline pieces
+    psStats *stat;              // Statistic to use when reducing the minor direction
+    // Outputs
+    psPolynomial1D *poly;       // Result of polynomial fit
+    psSpline1D *spline;         // Result of spline fit
+}
+pmOverscanOptions;
+
+pmOverscanOptions *pmOverscanOptionsAlloc(bool single, pmFit fitType, unsigned int order, psStats *stat);
+
+bool pmSubtractBias(pmReadout *in, pmOverscanOptions *overscanOpts,
+                    const pmReadout *bias, const pmReadout *dark);
+
+#if 0
+pmReadout *pmSubtractBias(pmReadout *in,                ///< The input pmReadout image
+                          void *fitSpec,                ///< A polynomial or spline, defining the fit type.
+                          const psList *overscans,      ///< A psList of overscan images
+                          pmOverscanAxis overScanAxis,  ///< Defines how overscans are applied
+                          psStats *stat,                ///< The statistic to be used in combining overscan data
+                          int nBin,                     ///< The amount of binning to be done image pixels.
+                          pmFit fit,                    ///< PM_FIT_SPLINE, PM_FIT_POLYNOMIAL, or PM_FIT_NONE
+                          const pmReadout *bias);       ///< A possibly NULL bias pmReadout which is to be subtracted
+#endif
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/detrend/pmSubtractSky.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmSubtractSky.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmSubtractSky.c	(revision 9533)
@@ -0,0 +1,727 @@
+/** @file  pmSubtractSky.c
+ *
+ *  This file will contain a module which will create a model of the
+ *  background sky and subtract that from the input image.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-15 09:49:01 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <pslib.h>
+#include "pmSubtractSky.h"
+
+// XXX: Get rid of the.  Create pmUtils.h
+psImage *p_psDetermineTrimmedImage(
+    pmReadout *in
+);
+
+/******************************************************************************
+DetermineNumBits(data): This routine takes an enum psStatsOptions as an
+argument and returns the number of non-zero bits.
+ 
+XXX: This code is duplicated in the ReadoutCombine file.
+ *****************************************************************************/
+static psS32 DetermineNumBits(psStatsOptions data)
+{
+    psTrace("psModules.detrend", 4, "Calling DetermineNumBits(0x%x)\n", data);
+
+    psS32 i;
+    psU64 tmpData = data;
+    psS32 numBits = 0;
+
+    for (i=0;i<(8 * sizeof(psStatsOptions));i++) {
+        if (0x0001 & tmpData) {
+            numBits++;
+        }
+        tmpData = tmpData >> 1;
+    }
+
+    psTrace("psModules.detrend", 4,
+            "Calling DetermineNumBits(0x%x) -> %d\n", data, numBits);
+    return(numBits);
+}
+
+/******************************************************************************
+getHighestPriorityStatOption(statOptions): this routine takes as input a
+psStats->options with multiple options set and returns one with a single
+option set according to the precedence set in the SDRS.
+ *****************************************************************************/
+static psU64 getHighestPriorityStatOption(psU64 statOptions)
+{
+    psTrace("psModules.detrend", 4,
+            "Calling getHighestPriorityStatOption(0x%x)\n", statOptions);
+
+    if (statOptions & PS_STAT_SAMPLE_MEAN) {
+        return(PS_STAT_SAMPLE_MEAN);
+    } else if (statOptions & PS_STAT_SAMPLE_MEDIAN) {
+        return(PS_STAT_SAMPLE_MEDIAN);
+    } else if (statOptions & PS_STAT_CLIPPED_MEAN) {
+        return(PS_STAT_CLIPPED_MEAN);
+    } else if (statOptions & PS_STAT_FITTED_MEAN) {
+        return(PS_STAT_FITTED_MEAN);
+    } else if (statOptions & PS_STAT_ROBUST_MEDIAN) {
+        return(PS_STAT_ROBUST_MEDIAN);
+    }
+    psError(PS_ERR_UNKNOWN, true, "Unallowable option requested for statistically binning image pixels.\n");
+    return(-1);
+    // XXX
+    //else if (statOptions & PS_STAT_ROBUST_MODE) {
+    //    return(PS_STAT_ROBUST_MODE);
+    //}
+}
+
+/******************************************************************************
+psImage *binImage(origImage, binFactor, statOptions): This routine takes an
+input psImage and scales it smaller by a factor of binFactor.  The statistic
+used in combining input pixels is specified in statOptions.
+ 
+XXX: use static vectors for myStats, binVector and binMask.
+XXX: I coded this before I was aware of a psLib reBin function.  I don't
+use this function in this module.  I'm keeping it here in the event that
+requirements change and we might need a custom reBin function.
+ *****************************************************************************/
+#if 0
+static psImage *binImage(psImage *origImage,
+                         int binFactor,
+                         psStatsOptions statOptions)
+{
+    psTrace("psModules.detrend", 4, "Calling binImage(%d)\n", binFactor);
+
+    if (binFactor <= 0) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: binImage(): binFactor is %d\n", binFactor);
+        return(origImage);
+    }
+    if (binFactor == 1) {
+        return(origImage);
+    }
+
+    psVector *binVector = psVectorAlloc(binFactor * binFactor, PS_TYPE_F32);
+    psVector *binMask = psVectorAlloc(binFactor * binFactor, PS_TYPE_U8);
+    psStats *myStats = psStatsAlloc(statOptions);
+
+    for (psS32 row = 0; row < origImage->numRows ; row+=binFactor) {
+        for (psS32 col = 0; col < origImage->numCols ; col+=binFactor) {
+            psS32 count = 0;
+            for (psS32 binRow = 0; binRow <= binFactor ; binRow++) {
+                for (psS32 binCol = 0; binCol <= binFactor ; binCol++) {
+                    if (((row + binRow) < origImage->numRows) &&
+                            ((col + binCol) < origImage->numCols)) {
+                        binVector->data.F32[count] =
+                            origImage->data.F32[row + binRow][col + binCol];
+                        binMask->data.U8[count] = 0;
+                    } else {
+                        binVector->data.F32[count] = 0.0;
+                        binMask->data.U8[count] = 1;
+                    }
+                    count++;
+                }
+            }
+            psStats *rc1 = psVectorStats(myStats, binVector, NULL, binMask, 1);
+            if (rc1 == NULL) {
+                psError(PS_ERR_UNKNOWN, false, "psVectorStats(): could not perform requested statistical operation.  Returning in image.\n");
+                return(origImage);
+            }
+            psF64 statValue;
+            psBool rc = p_psGetStatValue(rc1, &statValue);
+
+            if (rc == true) {
+                origImage->data.F32[row][col] = (psF32) statValue;
+            } else {
+                origImage->data.F32[row][col] = 0.0;
+                psLogMsg(__func__, PS_LOG_WARN,
+                         "WARNING: pmSubtractSky(), binImage(): p_psGetStatValue() was FALSE\n");
+            }
+        }
+    }
+    psFree(binVector);
+    psFree(binMask);
+    psFree(myStats);
+
+    psTrace("psModules.detrend", 4, "Exiting binImage(%d)\n", binFactor);
+    return(origImage);
+}
+#endif
+
+/******************************************************************************
+CalculatePolyTerms(xOrder, yOrder): this routine will calculate the number of
+coefficients (or terms) in a 2-D polynomial of order (xOrder, yOrder).
+ 
+XXX: Use your brain and figure out the analytical expression.
+ 
+XXX: Why isn't it simply (xOrder+1) * (yOrder+1)?
+ *****************************************************************************/
+static psS32 CalculatePolyTerms(psS32 xOrder, psS32 yOrder)
+{
+    psTrace("psModules.detrend", 4,
+            "Calling CalculatePolyTerms(%d, %d)\n", xOrder, yOrder);
+
+    psS32 maxOrder = PS_MAX(xOrder, yOrder);
+    psS32 localPolyTerms = 0;
+    psS32 order = 0;
+    psS32 num=0;
+
+    for (order=0;order<=maxOrder;order++) {
+        for (num=0;num<=order;num++) {
+            if (((order-num) <= xOrder) && (num <= yOrder)) {
+                localPolyTerms++;
+            }
+        }
+    }
+    psTrace("psModules.detrend", 4,
+            "Exiting CalculatePolyTerms(%d, %d) -> %d\n", xOrder, yOrder, localPolyTerms);
+    return(localPolyTerms);
+
+    //    return((xOrder+1) * (yOrder+1));
+}
+
+/******************************************************************************
+buildPolyTerms(): this routine computes a 2-D array polyTerms[][] that holds
+terms for the polynomial that is used to model the sky background.  We use
+this array primarily for convenience in computations involving sky model
+polynomials.  It is defined as:
+    polyTerms[i][0] = the power to which X is raised in the i-th term of in an
+    poly-order sky background polynomial.
+ 
+    polyTerms[i][1] = the power to which Y is raised in the i-th term of in an
+    poly-order sky background polynomial.
+ *****************************************************************************/
+static psS32 **buildPolyTerms(psS32 xOrder, psS32 yOrder)
+{
+    psTrace("psModules.detrend", 4,
+            "Calling buildPolyTerms(%d, %d)\n", xOrder, yOrder);
+
+    psS32 i=0;
+    psS32 order = 0;
+    psS32 num=0;
+    psS32 localPolyTerms = CalculatePolyTerms(xOrder, yOrder);
+    psS32 maxOrder = PS_MAX(xOrder, yOrder);
+
+    // Create the data structure which we hold the xy order of each coeff.
+    psS32 **polyTerms = (psS32 **) psAlloc(localPolyTerms * sizeof(psS32 *));
+    for (i=0; i < localPolyTerms ; i++) {
+        polyTerms[i] = (psS32 *) psAlloc(2 * sizeof(psS32));
+    }
+
+    i=0;
+    // This code segment loops through each term i in the polynomial and
+    // calculates the power to which x/y are raised in that i-th term.
+    // We first do the 0-order terms, then the 1-order terms, etc.
+    for (order=0;order<=maxOrder;order++) {
+        for (num=0;num<=order;num++) {
+            if (((order-num) <= xOrder) && (num <= yOrder)) {
+                polyTerms[i][0] = order-num;
+                polyTerms[i][1] = num;
+                i++;
+            }
+        }
+    }
+
+    if (psTraceGetLevel(".psModule.pmSubtractSky.buildPolyTerms") >= 10) {
+        for (i=0; i < localPolyTerms ; i++) {
+            printf("x^%d * y^%d\n", polyTerms[i][0], polyTerms[i][1]);
+        }
+    }
+
+    psTrace("psModules.detrend", 4,
+            "Exiting buildPolyTerms(%d, %d)\n", xOrder, yOrder);
+    return(polyTerms);
+}
+
+/******************************************************************************
+This procedure calculates various combinations of powers of x and y and stores
+them in the data structure p_psPolySums[][].  After it completes:
+ 
+    p_psPolySums[i][j] == x^i * y^j
+ 
+XXX: Use a psImage for the p_psPolySums data structure?
+XXX: p_psPolySums: should this be a global?  Did you get the storage classifier
+     and name correct?
+XXX: Use variable size arrays for p_psPolySums[][].
+XXX: Must initialize p_psPolySums[][]?
+ *****************************************************************************/
+#define PS_MAX_POLYNOMIAL_ORDER 20
+
+psF64 p_psPolySums[PS_MAX_POLYNOMIAL_ORDER+1][PS_MAX_POLYNOMIAL_ORDER+1];
+static void buildSums(psF64 x,
+                      psF64 y,
+                      psS32 xOrder,
+                      psS32 yOrder)
+{
+    psTrace("psModules.detrend", 4,
+            "Calling buildPolyTerms(%d, %d)\n", xOrder, yOrder);
+
+    psS32 i = 0;
+    psS32 j = 0;
+    psF64 xSum = 0.0;
+    psF64 ySum = 0.0;
+
+    xSum = 1.0;
+    ySum = 1.0;
+    for(i=0;i<=xOrder;i++) {
+        ySum = xSum;
+        for(j=0;j<=yOrder;j++) {
+            p_psPolySums[i][j] = ySum;
+            ySum*= y;
+        }
+        xSum*= x;
+    }
+    psTrace("psModules.detrend", 4,
+            "Exiting buildPolyTerms(%d, %d)\n", xOrder, yOrder);
+}
+
+/******************************************************************************
+ImageFitPolynomial(myPoly, dataImage, maskImage): this private routine takes
+an input image along with a mask and fits a polynomial to it.  The degree of
+the polynomial is specified by input parameter myPoly, and need not be
+symmetrical in orders of X and Y.  The polynomial must be type
+PS_POLYNOMIAL_ORD.  If there are not enough rows or columns in the input image
+for the order of the polynomial, then that order is reduced.  The algorithm
+used in this routine is based on that of the pilot project ADD, but is not
+documented anywhere.
+ 
+XXX: Different trace message facilities in use here.
+ *****************************************************************************/
+static psPolynomial2D *ImageFitPolynomial(
+    psPolynomial2D *myPoly,
+    psImage *dataImage,
+    psImage *maskImage)
+{
+    psTrace("psModules.detrend", 4,
+            "Calling ImageFitPolynomial()\n");
+    PS_ASSERT_POLY_NON_NULL(myPoly, NULL);
+    PS_ASSERT_POLY_TYPE(myPoly, PS_POLYNOMIAL_ORD, NULL);
+    PS_ASSERT_IMAGE_NON_NULL(dataImage, NULL);
+    PS_ASSERT_IMAGE_NON_EMPTY(dataImage, NULL);
+    PS_ASSERT_IMAGE_TYPE(dataImage, PS_TYPE_F32, NULL);
+    PS_ASSERT_IMAGE_NON_NULL(maskImage, NULL);
+    PS_ASSERT_IMAGE_NON_EMPTY(maskImage, NULL);
+    PS_ASSERT_IMAGE_TYPE(maskImage, PS_TYPE_U8, NULL);
+    PS_ASSERT_IMAGES_SIZE_EQUAL(dataImage, maskImage, NULL);
+    psS32 oldPolyX = -1;
+    psS32 oldPolyY = -1;
+
+    // The matrix equations become singular if there are more powers of X
+    // in myPoly then there are rows of the image.  I think.  Similarly for
+    // powers of Y and columns.  So.  Here we reduce the complexity of the
+    // polynomial if there are not enough rows/columns in the input image.
+
+    if ((myPoly->nX + 1) > dataImage->numRows) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: ImageFitPolynomial(): Reducing polynomial complexity in x-dimension.\n");
+        oldPolyX = myPoly->nX;
+        myPoly->nX = dataImage->numRows - 1;
+    }
+    if ((myPoly->nY + 1) > dataImage->numCols) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: ImageFitPolynomial(): Reducing polynomial complexity in y-dimension.\n");
+        oldPolyY = myPoly->nY;
+        myPoly->nY = dataImage->numCols - 1;
+    }
+    psS32 i;
+    psS32 j;
+    psS32 x;
+    psS32 y;
+    psS32 aRow;
+    psS32 aCol;
+    psS32 **polyTerms = buildPolyTerms(myPoly->nX, myPoly->nY);
+    // We determine how many coefficients will be in the polynomial that we
+    // are fitting to this image.
+    psS32 localPolyTerms = CalculatePolyTerms(myPoly->nX, myPoly->nY);
+    psImage *A = psImageAlloc(localPolyTerms, localPolyTerms, PS_TYPE_F64);
+    psImage *Aout = psImageAlloc(localPolyTerms, localPolyTerms, PS_TYPE_F64);
+    psVector *B = psVectorAlloc(localPolyTerms, PS_TYPE_F64);
+    B->n = B->nalloc;
+    psVector *outPerm = NULL;
+
+    //
+    // Initialize A matrix and B vector.
+    //
+    psImageInit(A, 0.0);
+    psVectorInit(B, 0.0);
+
+    //
+    // We build the A matrix and B vector.
+    //
+    for (x=0;x<dataImage->numRows;x++) {
+        for (y=0;y<dataImage->numCols;y++) {
+            if (maskImage->data.U8[x][y] == 0) {
+                buildSums((psF64) x, (psF64) y, myPoly->nX, myPoly->nY);
+
+                /************************************************************
+                This code dervies from equation (7) of the pilot ADD.  However,
+                it is not exactly the same in that the order of the polynomial
+                may be different in X And Y.
+
+                Equation (7) from the pilot ADD describes 16 linear equations.
+                The i-th equation is simply the partial derivative of the
+                sky background polynomial (1) w.r.t. to the i-th term in
+                that polynomial.  The i-th equation is stored in row i of
+                matrix A[][] (matrix A[][] has origin (1,1), not (0,0)).  To
+                compute A[i][j] we simply multiply the j-th term of the Sky
+                Background Polynomial (SBP) by the i-th term of SBP.
+                ************************************************************/
+                for (aRow=0;aRow<localPolyTerms;aRow++) {
+                    for (aCol=0;aCol<localPolyTerms;aCol++) {
+                        A->data.F64[aRow][aCol]+=
+                            (p_psPolySums[ polyTerms[aCol][0] ][ polyTerms[aCol][1] ] *
+                             p_psPolySums[ polyTerms[aRow][0] ][ polyTerms[aRow][1] ]);
+                    }
+                }
+                // Build the B[] vector, which is the right-hand side of (7).
+                for (i=0;i<localPolyTerms;i++) {
+                    B->data.F64[i]+= dataImage->data.F32[x][y] *
+                                     p_psPolySums[ polyTerms[i][0] ][ polyTerms[i][1] ];
+                }
+            }
+        }
+    }
+
+    if (psTraceGetLevel(".psModule.pmSubtractSky.ImageFitPolynomial") >= 8) {
+        for (aRow=0;aRow<localPolyTerms;aRow++) {
+            for (aCol=0;aCol<localPolyTerms;aCol++) {
+                printf("A[%d][%d] is %f\n", aRow, aCol,
+                       A->data.F64[aRow][aCol]);
+            }
+        }
+
+        for (i=0;i<=localPolyTerms;i++) {
+            printf("B[%d] is %f\n", i, B->data.F64[i]);
+        }
+    }
+
+    //
+    // Solve the matrix equations for the polynomial coefficients C.
+    // XXX: How do we know if these matrix operations were successful?
+    //
+    Aout = psMatrixLUD(Aout, &outPerm, A);
+    PS_ASSERT_IMAGE_NON_NULL(Aout, NULL);
+    PS_ASSERT_IMAGE_NON_EMPTY(Aout, NULL);
+    psVector *C = psVectorAlloc(localPolyTerms, PS_TYPE_F64);
+    C->n = C->nalloc;
+    psMatrixLUSolve(C, Aout, B, outPerm);
+
+    //
+    // Set the appropriate coefficients in the myPoly structure.
+    //
+    for (i=0;i<localPolyTerms;i++) {
+        myPoly->coeff[ polyTerms[i][0] ][ polyTerms[i][1] ] = C->data.F64[i];
+        psTrace("psModules.detrend", 6,
+                "myPoly->coeff[%d][%d] is %f\n", polyTerms[i][0], polyTerms[i][1], myPoly->coeff[ polyTerms[i][0] ][ polyTerms[i][1] ]);
+    }
+
+    //
+    // Free data structures that were allocated in this module.
+    //
+    for (i=0;i<localPolyTerms;i++) {
+        psFree(polyTerms[i]);
+    }
+    psFree(polyTerms);
+    psFree(A);
+    psFree(Aout);
+    psFree(B);
+    psFree(C);
+    psFree(outPerm);
+
+    //
+    // We restore the original size of the polynomial and set remaining
+    // coefficients to 0.0, if necessary.
+    //
+    // XXX: Verify this works after poly nOrder/nTerm change.
+    //
+    if (oldPolyX != -1) {
+        myPoly->nX = oldPolyX;
+        for (i=oldPolyX ; i < (1 + myPoly->nX) ; i++) {
+            for (j=0;j<(1 + myPoly->nY) ; j++) {
+                myPoly->coeff[i][j] = 0.0;
+            }
+        }
+    }
+    if (oldPolyY != -1) {
+        myPoly->nY = oldPolyY;
+        for (i=0 ; i < (1 + myPoly->nX) ; i++) {
+            for (j=oldPolyY;j < (1 + myPoly->nY) ; j++) {
+                myPoly->coeff[i][j] = 0.0;
+            }
+        }
+    }
+
+    psTrace("psModules.detrend", 4,
+            "Exiting ImageFitPolynomial()\n");
+    //    psTrace("psModules.detrend", 4,
+    //            "---- ImageFitPolynomial() end successfully ----\n");
+    return(myPoly);
+}
+
+
+/******************************************************************************
+pmReadout pmSubtractSky():
+ 
+XXX: use static vectors for myStats, and the binned image
+ 
+XXX: The SDR is silent about types.  PS_TYPE_F32 is implemented here.
+ 
+XXX: Sync the psTrace message facilities.
+ *****************************************************************************/
+pmReadout *pmSubtractSky(pmReadout *in,
+                         void *fitSpec,
+                         psFit fit,
+                         psS32 binFactor,
+                         psStats *stats,
+                         psF32 clipSD)
+{
+    PS_ASSERT_READOUT_NON_NULL(in, NULL);
+    PS_ASSERT_READOUT_NON_EMPTY(in, NULL);
+    PS_ASSERT_READOUT_TYPE(in, PS_TYPE_F32, NULL);
+    PS_WARN_PTR_NON_NULL(in->parent);
+    if (in->parent != NULL) {
+        PS_WARN_PTR_NON_NULL(in->parent->concepts);
+    }
+    psTrace("psModules.detrend", 4,
+            "---- pmSubtractSky() begin ----\n");
+
+    if ((fit != PM_FIT_NONE) &&
+            (fit != PM_FIT_POLYNOMIAL) &&
+            (fit != PM_FIT_SPLINE)) {
+        psError(PS_ERR_UNKNOWN, true, "psFit is unallowable (%d).  Returning in image.\n", fit);
+        return(in);
+    }
+
+    psStatsOptions statOptions = 0;
+
+    //
+    // Return the original input readout if the fit specs are poorly defined.
+    // No warning or error messages should be generated.
+    //
+    if ((fitSpec == NULL) ||
+            ((fit == PM_FIT_NONE) || (fit == PM_FIT_SPLINE))) {
+        //        psLogMsg(__func__, PS_LOG_WARN, "Fit specs are poorly defined.  Returning in image.\n");
+        return(in);
+    }
+
+    //
+    // Determine trimmed image from metadata.
+    //
+
+    psImage *trimmedImg = p_psDetermineTrimmedImage(in);
+    psImage *binnedImage = NULL;
+    psPolynomial2D *myPoly = NULL;
+    psImage *binnedMaskImage = NULL;
+    psU32 oldStatOptions = 0;
+
+    //
+    // Determine which statistic to use when binning pixels, if any.
+    //
+    if (stats != NULL) {
+        statOptions = stats->options;
+        if (1 < DetermineNumBits(statOptions)) {
+            psLogMsg(__func__, PS_LOG_WARN, "WARNING: Multiple statistical options have been requested.\n");
+            statOptions = getHighestPriorityStatOption(statOptions);
+            if (statOptions == -1) {
+                psError(PS_ERR_UNKNOWN, true, "Not allowable stats->option was specified.  Returning in image.\n");
+                return(in);
+            }
+            // Save old input "stats" parameter.
+            oldStatOptions = stats->options;
+            stats->options = statOptions;
+        }
+        if (0 == DetermineNumBits(statOptions)) {
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "WARNING: pmSubtractSky(): no stats->options was requested\n");
+        }
+    }
+
+    //
+    // Generate required warning messages.
+    //
+    if (binFactor <= 0) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: pmSubtractSky(): binFactor is %d\n", binFactor);
+    }
+    if (stats == NULL) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: pmSubtractSky(): input parameter stats is NULL\n");
+    }
+
+    //
+    // Bin the input image according to input parameters.
+    // Create a new binned image mask.
+    //
+    if ((binFactor <= 1) || (stats == NULL) || (0 == DetermineNumBits(statOptions))) {
+        // No binning is required here.  Simply create a copy of the image
+        // and a mask.
+        binnedImage = psImageCopy(binnedImage, trimmedImg, PS_TYPE_F32);
+        if (binnedImage == NULL) {
+            psError(PS_ERR_UNKNOWN, false, "psImageCopy() returned NULL.  Returning in image.\n");
+            return(in);
+        }
+
+        if (in->mask != NULL) {
+            binnedMaskImage = psImageCopy(binnedMaskImage, in->mask, PS_TYPE_U8);
+            if (binnedMaskImage == NULL) {
+                psError(PS_ERR_UNKNOWN, false, "psImageCopy() returned NULL.  Returning in image.\n");
+                psFree(binnedImage);
+                return(in);
+            }
+        } else {
+            binnedMaskImage = psImageAlloc(binnedImage->numCols,
+                                           binnedImage->numRows,
+                                           PS_TYPE_U8);
+            psImageInit(binnedMaskImage, 0);
+        }
+    } else {
+        binnedImage = psImageRebin(NULL, trimmedImg, in->mask, 0, binFactor, stats);
+        if (binnedImage == NULL) {
+            psError(PS_ERR_UNKNOWN, false, "psImageRebin() returned NULL.  Returning in image.\n");
+            return(in);
+        }
+        binnedMaskImage = psImageAlloc(binnedImage->numCols,
+                                       binnedImage->numRows,
+                                       PS_TYPE_U8);
+        psImageInit(binnedMaskImage, 0);
+    }
+    psTrace("psModules.detrend", 4,
+            "binnedImage size is (%d, %d)\n", binnedImage->numRows, binnedImage->numCols);
+
+    //
+    // Clip pixels that are outside the acceptable range.
+    //
+    if (clipSD <= 0.0) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: pmSubtractSky(): clipSD is %f\n", clipSD);
+    } else {
+        // Determine the mean and standard deviation of the binned image.
+        psStats *myStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+        if (!psImageStats(myStats, binnedImage, NULL, 0)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Couldn't get statistics for image.\n");
+            return NULL;
+        }
+        psF64 binnedMean = myStats->sampleMean;
+        psF64 binnedStdev = myStats->sampleStdev;
+        psFree(myStats);
+        psTrace("psModules.detrend", 6,
+                "binned StDev is %f\n", binnedStdev);
+
+        // Clip all pixels which are more than clipSD sigmas from the mean.
+        psTrace("psModules.detrend", 6,
+                "clipSD is %f\n", clipSD);
+
+        for (psS32 row = 0; row < binnedImage->numRows ; row++) {
+            for (psS32 col = 0; col < binnedImage->numCols ; col++) {
+                if (fabs(binnedImage->data.F32[row][col] - binnedMean) >
+                        (clipSD * binnedStdev)) {
+                    binnedMaskImage->data.U8[row][col] = 1;
+                }
+            }
+        }
+    }
+
+    //
+    // Fit the polynomial to the binned image
+    //
+    if (fit == PM_FIT_POLYNOMIAL) {
+        myPoly = (psPolynomial2D *) fitSpec;
+        PS_ASSERT_POLY_NON_NULL(myPoly, NULL);
+        PS_ASSERT_POLY_TYPE(myPoly, PS_POLYNOMIAL_ORD, NULL);
+
+        myPoly = ImageFitPolynomial(myPoly, binnedImage, binnedMaskImage);
+
+        if (myPoly != NULL) {
+            // Set the pixels in the binned image to that of the polynomial.
+            binnedImage = psImageEvalPolynomial(binnedImage, myPoly);
+            if (binnedImage == NULL) {
+                psError(PS_ERR_UNKNOWN, false, "psImageEvalPolynomial() returned NULL.  Returning in image.\n");
+                psFree(binnedMaskImage);
+                if (!((binFactor <= 1) || (stats == NULL))) {
+                    psFree(binnedImage);
+                }
+                if (oldStatOptions != 0) {
+                    stats->options = statOptions;
+                }
+                return(in);
+            }
+        } else {
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "WARNING: pmSubtractSky(): could not model sky with a polynomial.\n");
+            psFree(binnedMaskImage);
+            if (!((binFactor <= 1) || (stats == NULL))) {
+                psFree(binnedImage);
+            }
+            if (oldStatOptions != 0) {
+                stats->options = statOptions;
+            }
+            return(in);
+        }
+    } else {
+        // We shouldn't get here since we check this above.
+        psError(PS_ERR_UNKNOWN, true, "Unallowable fit type.  Returning in image.\n");
+        psFree(binnedMaskImage);
+        if (!((binFactor <= 1) || (stats == NULL))) {
+            psFree(binnedImage);
+        }
+        if (oldStatOptions != 0) {
+            stats->options = statOptions;
+        }
+        return(in);
+    }
+
+    //
+    //Subtract the polynomially fitted image from the original image
+    //
+    if (binFactor <= 1) {
+        // The binned image is the same size as the original image.
+        for (psS32 row = 0; row < trimmedImg->numRows ; row++) {
+            for (psS32 col = 0; col < trimmedImg->numCols ; col++) {
+                trimmedImg->data.F32[row][col]-= binnedImage->data.F32[row][col];
+            }
+        }
+    } else {
+        for (psS32 row = 0; row < trimmedImg->numRows ; row++) {
+            for (psS32 col = 0; col < trimmedImg->numCols ; col++) {
+                // We calculate the F32 value of the pixel coordinates in the
+                // binned image and then use a pixel interpolation routine to
+                // determine the value of the pixel at that location.
+                psF32 binRowF64 = ((psF32) row) / ((psF32) binFactor);
+                psF32 binColF64 = ((psF32) col) / ((psF32) binFactor);
+
+                // We add 0.5 to the pixel locations since the pixel
+                // interpolation routine defines the location of pixel
+                // (i, j) as (i+0.5, j+0.5).
+                binRowF64+= 0.5;
+                binColF64+= 0.5;
+
+                psF32 binPixel = (psF32) psImagePixelInterpolate(
+                                     binnedImage, binColF64, binRowF64,
+                                     NULL, 0, 0.0, PS_INTERPOLATE_BILINEAR);
+                trimmedImg->data.F32[row][col]-= binPixel;
+
+                psTrace("psModules.detrend", 8,
+                        "image[%d][%d] <--> binnedImage[%.2f][%.2f]: %f\n",
+                        row, col, binRowF64-0.5, binColF64-0.5, binPixel);
+            }
+        }
+
+    }
+    psFree(binnedMaskImage);
+    psFree(binnedImage);
+    if (oldStatOptions != 0) {
+        stats->options = statOptions;
+    }
+
+    psTrace("psModules.detrend", 4,
+            "---- pmSubtractSky() exit successfully ----\n");
+    return(in);
+}
Index: /tags/RHL_v1_1/psModules/src/detrend/pmSubtractSky.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/pmSubtractSky.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/pmSubtractSky.h	(revision 9533)
@@ -0,0 +1,40 @@
+/** @file  pmSubtractSky.h
+ *
+ *  This file will contain a module which will create a model of the
+ *  background sky and subtract that from the input image.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-05-01 01:56:29 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#if !defined(PM_SUBTRACT_SKY_H)
+#define PM_SUBTRACT_SKY_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include<stdio.h>
+#include<math.h>
+#include "pslib.h"
+#include "pmFPA.h"
+
+// XXX: this is pmFit in pmSubtractBias.c, named psFit here.
+typedef enum {
+    PM_FIT_NONE,                              ///< No fit
+    PM_FIT_POLYNOMIAL,                        ///< Fit polynomial
+    PM_FIT_SPLINE                             ///< Fit cubic splines
+} psFit;
+
+pmReadout *pmSubtractSky(pmReadout *in,
+                         void *fitSpec,
+                         psFit fit,
+                         int binFactor,
+                         psStats *stats,
+                         float clipSD);
+#endif
Index: /tags/RHL_v1_1/psModules/src/detrend/psIOBuffer.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/psIOBuffer.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/psIOBuffer.c	(revision 9533)
@@ -0,0 +1,113 @@
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <pslib.h>
+#include "psIOBuffer.h"
+
+static void psIOBufferFree (psIOBuffer *buffer)
+{
+    if (buffer == NULL)
+        return;
+
+    psFree (buffer->data);
+    return;
+}
+
+psIOBuffer *psIOBufferAlloc (int nBuffer)
+{
+
+    psIOBuffer *buffer = (psIOBuffer *)psAlloc(sizeof(psIOBuffer));
+    psMemSetDeallocator(buffer, (psFreeFunc) psIOBufferFree);
+
+    buffer->data = (char *) psAlloc (nBuffer);
+
+    buffer->nAlloc = nBuffer;
+    buffer->nReset = nBuffer;
+    buffer->nBlock = nBuffer / 2;
+    buffer->n = 0;
+    return (buffer);
+}
+
+bool psIOBufferFlush (psIOBuffer *buffer)
+{
+
+    if (buffer == NULL)
+        return false;
+    buffer->n = 0;
+    buffer->nAlloc = buffer->nReset;
+    psRealloc (buffer->data, buffer->nAlloc);
+    bzero (buffer->data, buffer->nAlloc);
+
+    return true;
+}
+
+int psIOBufferRead (psIOBuffer *buffer, int fd)
+{
+
+    int Nread, Nfree;
+
+    if (fd == 0) {
+        /* pipe is closed */
+        return (0);
+    }
+
+    Nfree = buffer->nAlloc - buffer->n;
+
+    // extend the data block if needed
+    if (Nfree < buffer->nBlock) {
+        buffer->nAlloc += 2*buffer->nBlock;
+        psRealloc (buffer->data, buffer->nAlloc);
+        Nfree = buffer->nAlloc - buffer->n;
+        bzero (buffer->data + buffer->n, Nfree);
+    }
+
+    // attempt to read from the fd into the buffer
+    Nread = read (fd, &buffer->data[buffer->n], buffer->nBlock);
+
+    if (Nread >= 0) {
+        buffer->n += Nread;
+        buffer->data[buffer->n] = 0;
+        return (Nread);
+    }
+
+    // check on exit status (try again if waiting for non-blocking fd)
+    if (Nread == -1) {
+        switch (errno) {
+        case EAGAIN:
+        case EIO:
+            /** no data available in pipe **/
+            return (-1);
+        default:
+            /** error reading from pipe **/
+            psError (PS_ERR_IO, true, "error on psIOBufferRead");
+            return (-2);
+        }
+    }
+    return (Nread);
+}
+
+/* read until buffer is empty (Nmax retries) */
+int psIOBufferReadEmpty (psIOBuffer *buffer, int Nmax, int fd)
+{
+
+    int i, status;
+
+    status = -1;
+    for (i = 0; (status != 0) && (i < Nmax); i++) {
+        status = psIOBufferRead (buffer, fd);
+        if (status == -2)
+            return false;
+        if (status == -1)
+            usleep (10000);
+        if (status > 0)
+            i = 0;
+    }
+    if (status == -1)
+        return false;
+    return true;
+}
Index: /tags/RHL_v1_1/psModules/src/detrend/psIOBuffer.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/psIOBuffer.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/psIOBuffer.h	(revision 9533)
@@ -0,0 +1,34 @@
+/** @file  psPipe.h
+*
+*  @brief 3-stream pipe 
+*
+*  @ingroup misc
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-10-10 01:01:09 $
+*
+*  Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+*/
+
+#ifndef PS_IO_BUFFER_H
+#define PS_IO_BUFFER_H
+
+typedef struct
+{
+    char *data;
+    int nAlloc;    // current size of allocated buffer
+    int nReset;    // size to set buffer after flush
+    int nBlock;    // number of bytes to try to read at a time
+    int n;    // current size of filled data
+}
+psIOBuffer;
+
+// psIOBuffer functions
+psIOBuffer *psIOBufferAlloc (int nBuffer);
+bool psIOBufferFlush (psIOBuffer *buffer);
+int psIOBufferRead (psIOBuffer *buffer, int fd);
+int psIOBufferReadEmpty (psIOBuffer *buffer, int maxRetries, int fd);
+
+# endif
Index: /tags/RHL_v1_1/psModules/src/detrend/psPipe.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/psPipe.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/psPipe.c	(revision 9533)
@@ -0,0 +1,210 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <pslib.h>
+#include <psPipe.h>
+
+void closePipes (int *stdin_fd, int *stdout_fd, int *stderr_fd)
+{
+
+    if (stdin_fd[0]  != 0)
+        close (stdin_fd[0]);
+    if (stdin_fd[1]  != 0)
+        close (stdin_fd[0]);
+    if (stdout_fd[0] != 0)
+        close (stdout_fd[0]);
+    if (stdout_fd[1] != 0)
+        close (stdout_fd[1]);
+    if (stderr_fd[0] != 0)
+        close (stderr_fd[0]);
+    if (stderr_fd[1] != 0)
+        close (stderr_fd[1]);
+}
+
+static void psPipeFree (psPipe *pipe)
+{
+    return;
+}
+
+psPipe *psPipeAlloc ()
+{
+    psPipe *pipe = (psPipe *)psAlloc(sizeof(psPipe));
+    psMemSetDeallocator(pipe, (psFreeFunc) psPipeFree);
+
+    pipe->stdin  = 0;
+    pipe->stdout = 0;
+    pipe->stderr = 0;
+    return (pipe);
+}
+
+psPipe *psPipeOpen (char *command)
+{
+
+    int stdin_fd[2], stdout_fd[2], stderr_fd[2], status;
+    pid_t pid;
+
+    bzero (stdin_fd,  2*sizeof(int));
+    bzero (stdout_fd, 2*sizeof(int));
+    bzero (stderr_fd, 2*sizeof(int));
+
+    if (pipe (stdin_fd)  < 0) {
+        psError (PS_ERR_UNKNOWN, true, "cannot create pipe file descriptor");
+        closePipes (stdin_fd, stdout_fd, stderr_fd);
+        return NULL;
+    }
+    if (pipe (stdout_fd) < 0) {
+        psError (PS_ERR_UNKNOWN, true, "cannot create pipe file descriptor");
+        closePipes (stdin_fd, stdout_fd, stderr_fd);
+        return NULL;
+    }
+    if (pipe (stderr_fd) < 0) {
+        psError (PS_ERR_UNKNOWN, true, "cannot create pipe file descriptor");
+        closePipes (stdin_fd, stdout_fd, stderr_fd);
+        return NULL;
+    }
+
+    psArray *cmd = psStringSplitArray (command, " ", false);
+    if (cmd->n <= 0) {
+        psError (PS_ERR_UNKNOWN, true, "empty command for pipe");
+        psFree (cmd);
+        closePipes (stdin_fd, stdout_fd, stderr_fd);
+        return NULL;
+    }
+
+    // create the command line array needed by execvp
+    char **argv = (char **)psAlloc((cmd->n+1)*sizeof(char *));
+    for (int i = 0; i < cmd->n; i++) {
+        argv[i] = cmd->data[i];
+    }
+    argv[cmd->n] = NULL;
+
+    pid = fork ();
+    if (!pid) { /* must be child process */
+        /* close the other ends of the pipes */
+        close (stdin_fd[1]);
+        close (stdout_fd[0]);
+        close (stderr_fd[0]);
+
+        /* tie our ends of the pipes to stdin, stdout, stderr */
+        dup2 (stdin_fd[0],  STDIN_FILENO);
+        dup2 (stdout_fd[1], STDOUT_FILENO);
+        dup2 (stderr_fd[1], STDERR_FILENO);
+
+        /* set all three unblocking */
+        setvbuf (stdin,  (char *) NULL, _IONBF, BUFSIZ);
+        setvbuf (stdout, (char *) NULL, _IONBF, BUFSIZ);
+        setvbuf (stderr, (char *) NULL, _IONBF, BUFSIZ);
+
+        status = execvp (argv[0], argv);
+
+        // this statement exits the child, not the parent, process
+        exit (1);
+    }
+    psFree (cmd);
+    psFree (argv);
+
+    if (pid == -1) {
+        psError (PS_ERR_UNKNOWN, true, "unable to create child process");
+        closePipes (stdin_fd, stdout_fd, stderr_fd);
+        return NULL;
+    }
+
+    /* close the other ends of the pipes */
+    close (stdin_fd[0]);
+    stdin_fd[0]  = 0;
+    close (stdout_fd[1]);
+    stdout_fd[1] = 0;
+    close (stderr_fd[1]);
+    stderr_fd[1] = 0;
+
+    /* make the pipes non-blocking */
+    fcntl (stdin_fd[1],  F_SETFL, O_NONBLOCK);
+    fcntl (stdout_fd[0], F_SETFL, O_NONBLOCK);
+    fcntl (stderr_fd[0], F_SETFL, O_NONBLOCK);
+
+    psPipe *pipe = psPipeAlloc();
+
+    pipe->pid    = pid;
+    pipe->stdin  = stdin_fd[1];
+    pipe->stdout = stdout_fd[0];
+    pipe->stderr = stderr_fd[0];
+
+    return (pipe);
+}
+
+// this function returns the exit status of the called function
+// or a value > 255 on an error
+int psPipeClose (psPipe *pipe)
+{
+    int close_status;
+    int exit_status;
+    int wait_status;
+    int result;
+
+    PS_ASSERT_PTR_NON_NULL(pipe, false);
+
+    close_status = true;
+    if (close (pipe->stdin) != 0) {
+        psError(PS_ERR_IO, true, "error closing the pipe stdin (pid %d, error %s)\n", pipe->pid, strerror(errno));
+        close_status = false;
+    }
+    if (close (pipe->stdout) != 0) {
+        psError(PS_ERR_IO, true, "error closing the pipe stdout (pid %d, error %s)\n", pipe->pid, strerror(errno));
+        close_status = false;
+    }
+    if (close (pipe->stderr) != 0) {
+        psError(PS_ERR_IO, true, "error closing the pipe sterr (pid %d, error %s)\n", pipe->pid, strerror(errno));
+        close_status = false;
+    }
+
+    // we expect the child process to have exited.
+    // wait for the exit condition, but no longer than 100ms
+    for (int i = 0; i < 10; i++) {
+        result = waitpid (pipe->pid, &wait_status, WNOHANG);
+        switch (result) {
+        case -1:   // error on waitpid
+            switch (errno) {
+            case ECHILD:
+                psError(PS_ERR_IO, true, "unknown PID, not a child process: %d\n", pipe->pid);
+                return 0x100;
+            default:
+                psAbort ("psPipeClose", "unexpected response to waitpid: %d\n", result);
+            }
+            break;
+
+        case 0:   // child not yet exited
+            usleep (10000);
+            continue;
+
+        default:
+            if (result != pipe->pid) {
+                psAbort ("psPipeClose", "waitpid error: mis-matched PID (%d vs %d).  programming error\n", result, pipe->pid);
+            }
+            if (WIFEXITED(wait_status)) {
+                exit_status = WEXITSTATUS(wait_status);
+                if (close_status) {
+                    return exit_status;
+                } else {
+                    return (0x100);
+                }
+            }
+            if (WIFSIGNALED(wait_status)) {
+                psError(PS_ERR_IO, true, "job %d exited on signal %d\n", pipe->pid, WTERMSIG(wait_status));
+                return (0x100 + WTERMSIG(wait_status));
+            }
+            if (WIFSTOPPED(wait_status)) {
+                psAbort ("psPipeClose", "waitpid returns 'stopped' programming error\n");
+            }
+        }
+    }
+    psError(PS_ERR_IO, true, "child process pid %d did not exit\n", pipe->pid);
+    return 0x100;
+}
Index: /tags/RHL_v1_1/psModules/src/detrend/psPipe.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/detrend/psPipe.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/detrend/psPipe.h	(revision 9533)
@@ -0,0 +1,33 @@
+/** @file  psPipe.h
+*
+*  @brief 3-stream pipe 
+*
+*  @ingroup misc
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-10-10 01:01:09 $
+*
+*  Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+*/
+
+#ifndef PS_PIPE_H
+#define PS_PIPE_H
+
+// move these to pslib??
+typedef struct
+{
+    int pid;
+    int stdin;
+    int stdout;
+    int stderr;
+}
+psPipe;
+
+// psPipe functions
+psPipe *psPipeAlloc ();
+psPipe *psPipeOpen (char *command);
+int     psPipeClose (psPipe *pipe);
+
+# endif
Index: /tags/RHL_v1_1/psModules/src/imcombine/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/src/imcombine/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/imcombine/.cvsignore	(revision 9533)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/RHL_v1_1/psModules/src/imcombine/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/src/imcombine/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/imcombine/Makefile.am	(revision 9533)
@@ -0,0 +1,16 @@
+noinst_LTLIBRARIES = libpsmodulesimcombine.la
+
+libpsmodulesimcombine_la_CPPFLAGS = $(SRCINC) $(PSMODULES_CFLAGS)
+libpsmodulesimcombine_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+
+libpsmodulesimcombine_la_SOURCES  = \
+     pmImageCombine.c \
+     pmImageSubtract.c \
+     pmReadoutCombine.c
+
+pkginclude_HEADERS = \
+    pmImageCombine.h \
+    pmImageSubtract.h \
+    pmReadoutCombine.h
+
+CLEANFILES = *~
Index: /tags/RHL_v1_1/psModules/src/imcombine/pmImageCombine.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/imcombine/pmImageCombine.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/imcombine/pmImageCombine.c	(revision 9533)
@@ -0,0 +1,671 @@
+/** @file  pmImageCombine.c
+ *
+ *  This file will perform image combination of several images of the
+ *  same field, produce a list of questionable pixels, then tag some
+ *  of those pixels as cosmic rays.
+ *
+ *  @author Paul Price, IfA (original prototype)
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-15 09:49:01 $
+ *
+ *  XXX: pmRejectPixels() has a known bug with the pmImageTransform() call.
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <config.h>
+#include <stdio.h>
+#include <math.h>
+#include "pslib.h"
+
+//
+// The following macros define how big the initial pixel list will be, and
+// how much it should be incremented when realloc'ed.
+//
+#define PS_COMBINE_IMAGE_INITIAL_PIXEL_LIST_LENGTH 100
+#define PS_COMBINE_IMAGE_INITIAL_PIXEL_LIST_LENGTH_INC 100
+#define PS_COMBINE_IMAGE_MAX_QUESTIONABLE_PIXELS 1000
+/******************************************************************************
+pmCombineImages(combine, questionablePixels, images, errors, masks, maskVal,
+                pixels, numIter, sigmaClip, stats)
+ 
+XXX: Allocate a dummy psStats structure so that we don't destroy away its data.
+ *****************************************************************************/
+psImage *pmCombineImages(
+    psImage *combine,                   ///< Combined image (output)
+    psArray **questionablePixels,       ///< Array of rejection masks
+    const psArray *images,              ///< Array of input images
+    const psArray *errors,              ///< Array of input error images
+    const psArray *masks,               ///< Array of input masks
+    psU32 maskVal,                      ///< Mask value
+    const psPixels *pixels,             ///< Pixels to combine
+    psS32 numIter,                      ///< Number of rejection iterations
+    psF32 sigmaClip,                    ///< Number of standard deviations at which to reject
+    psStats *stats)               ///< Statistics to use in the combination
+{
+
+    PS_ASSERT_PTR_NON_NULL(images, combine);
+    psU32 numImages = images->n;
+    psTrace("psModules.imcombine", 3, "Calling pmCombineImages(%ld)\n", images->n);
+
+    if (errors != NULL) {
+        if (images->n != errors->n) {
+            psError(PS_ERR_UNKNOWN, true, "images and errors args must have same length (%ld != %ld)\n",
+                    images->n, errors->n);
+            return(combine);
+        }
+    }
+    if (masks != NULL) {
+        if (images->n != masks->n) {
+            psError(PS_ERR_UNKNOWN, true, "images and masks args must have same length (%ld != %ld)\n",
+                    images->n, masks->n);
+            return(combine);
+        }
+    }
+
+    psImage *tmpImg = (psImage *) images->data[0];
+    psU32 numRows = tmpImg->numRows;
+    psU32 numCols = tmpImg->numCols;
+
+    //
+    // Check that all images have the appropriate size and type.
+    //
+    for (psS32 i = 0 ; i < numImages ; i++) {
+        psImage *tmpDataImg = (psImage *) images->data[i];
+        PS_ASSERT_IMAGE_NON_NULL(tmpDataImg, combine);
+        PS_ASSERT_IMAGE_TYPE(tmpDataImg, PS_TYPE_F32, combine);
+        if ((tmpDataImg->numRows != numRows) || (tmpDataImg->numCols != numCols)) {
+            psError(PS_ERR_UNKNOWN, true, "image %d has size (%d, %d); should be (%d, %d).\n",
+                    i, tmpDataImg->numRows, tmpDataImg->numCols, numRows, numCols);
+        }
+
+        if (errors != NULL) {
+            psImage *tmpErrorImg = (psImage *) errors->data[i];
+            PS_ASSERT_IMAGE_NON_NULL(tmpErrorImg, combine);
+            PS_ASSERT_IMAGE_TYPE(tmpErrorImg, PS_TYPE_F32, combine);
+            PS_ASSERT_IMAGES_SIZE_EQUAL(tmpDataImg, tmpErrorImg, NULL);
+        }
+
+        if (masks != NULL) {
+            psImage *tmpMaskImg = (psImage *) masks->data[i];
+            PS_ASSERT_IMAGE_NON_NULL(tmpMaskImg, combine);
+            PS_ASSERT_IMAGE_TYPE(tmpMaskImg, PS_TYPE_U8, combine);
+            PS_ASSERT_IMAGES_SIZE_EQUAL(tmpDataImg, tmpMaskImg, NULL);
+        }
+    }
+    PS_ASSERT_PTR_NON_NULL(stats, combine);
+    psStatsOptions statistic = psStatsSingleOption(stats->options);
+    if (statistic == 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Multiple or no statistics options set: %x\n", statistic);
+        return combine;
+    }
+
+    // Allocate and initialize the combined image, if necessary.
+    if (combine == NULL) {
+        combine = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        if (pixels != NULL) {
+            psImageInit(combine, 0.0);
+        }
+    }
+
+    //
+    // Allocate the questionablePixels psArray, if necesssary, then create a psPixels
+    // struct for each image.
+    //
+    if (*questionablePixels == NULL) {
+        *questionablePixels = psArrayAlloc(numImages);
+        (*questionablePixels)->n = numImages;
+    } else if ((*questionablePixels)->n != numImages) {
+        *questionablePixels = psArrayRealloc(*questionablePixels, numImages);
+    }
+    for (psS32 im = 0 ; im < numImages ; im++) {
+        psFree((*questionablePixels)->data[im]);
+        ((*questionablePixels)->data[im]) =
+            (psPtr *) psPixelsAlloc(PS_COMBINE_IMAGE_INITIAL_PIXEL_LIST_LENGTH);
+        ((psPixels *) ((*questionablePixels)->data[im]))->n = 0;
+    }
+    //
+    // qpPtr is used to maintain a count of the questionable pixels for each image.
+    //
+    psVector *qpPtr = psVectorAlloc(numImages, PS_TYPE_S32);
+    psVectorInit(qpPtr, 0);
+    qpPtr->n = numImages;
+    //
+    // Allocate the necessary psVectors for the call to psVectorStats().
+    // These vectors will be used whether we are combining a list of pixels,
+    // or every pixel in the input images.
+    //
+    psVector *pixelData = psVectorAlloc(numImages, PS_TYPE_F32);
+    pixelData->n = pixelData->nalloc;
+    psVector *pixelMask = NULL;
+    if (masks != NULL) {
+        pixelMask = psVectorAlloc(numImages, PS_TYPE_U8);
+        psVectorInit(pixelMask, 0);
+        pixelMask->n = numImages;
+    }
+
+    psVector *pixelErrors = NULL;
+    if (errors != NULL) {
+        pixelErrors = psVectorAlloc(numImages, PS_TYPE_F32);
+        psVectorInit(pixelErrors, 1.0);
+        pixelErrors->n = numImages;
+    }
+
+    if (pixels != NULL) {
+        // Only those specified pixels should be combined.
+
+        psStats *stdevStats = psStatsAlloc(PS_STAT_SAMPLE_STDEV);
+
+        for (psS32 p = 0 ; p < pixels->n ; p++) {
+            // Must initialize the mask to 0 for every pixel.
+            psVectorInit(pixelMask, 0);
+            psS32 col = (pixels->data[p]).x;
+            psS32 row = (pixels->data[p]).y;
+
+            //
+            // Loop through each image, extract the pixel/mask/error data
+            // into psVectors.
+            //
+            for (psS32 im = 0 ; im < numImages ; im++) {
+                // Set the pixel data
+                pixelData->data.F32[im] =       ((psImage *) images->data[im])->data.F32[row][col];
+                // Set the pixel mask data, if necessary
+                if (masks != NULL) {
+                    pixelMask->data.U8[im] =   ((psImage *) masks->data[im])->data.F32[row][col];
+                }
+
+                // Set the pixel error data, if necessary
+                if (errors != NULL) {
+                    pixelErrors->data.F32[im] = ((psImage *) errors->data[im])->data.F32[row][col];
+                }
+            }
+
+            //
+            // Iterate on the pixels, rejecting outliers
+            //
+            for (psS32 iter = 0 ; iter < numIter ; iter++) {
+                // Combine all the pixels, using the specified stat.
+
+                stats = psVectorStats(stats, pixelData, pixelErrors, pixelMask, maskVal);
+                psF64 combinedPixel = psStatsGetValue(stats, statistic);
+
+                if (iter == 0) {
+                    combine->data.F32[row][col] = (psF32) combinedPixel;
+                }
+
+                //
+                // Reject all pixels that lie more that sigmaClip standard deviations from
+                // the combined pixel value.
+                //
+                psS32 numRejects = 0;
+                for (psS32 im = 0 ; im < numImages ; im++) {
+                    stdevStats = psVectorStats(stdevStats, pixelData, pixelErrors, pixelMask, maskVal);
+                    psF64 stdev = stdevStats->sampleStdev;
+
+                    if (!(pixelMask->data.U8[im] & maskVal)) {
+                        if (fabs(pixelData->data.F32[im]) >
+                                (fabs(sigmaClip * stdev) + fabs(combinedPixel))) {
+                            numRejects++;
+                            pixelMask->data.U8[im] = maskVal;
+                            //
+                            // XXX: These data structures indirections are getting complicated.
+                            //
+                            psS32 ptr = qpPtr->data.S32[im];
+                            psPixels *pixelListPtr = ((psPixels *) ((*questionablePixels)->data[im]));
+
+                            if (ptr >= pixelListPtr->nalloc) {
+                                (*questionablePixels)->data[im] =
+                                    (psPtr *) psPixelsRealloc(((psPixels *) ((*questionablePixels)->data[im])),
+                                                              ((((psPixels *) ((*questionablePixels)->data[im]))->nalloc) +
+                                                               PS_COMBINE_IMAGE_INITIAL_PIXEL_LIST_LENGTH_INC));
+                                // XXX: Can the realloc() fail?  Must we check for NULL?
+                            }
+                            ((psPixels *) ((*questionablePixels)->data[im]))->data[ptr].x = col;
+                            ((psPixels *) ((*questionablePixels)->data[im]))->data[ptr].y = row;
+                            (qpPtr->data.S32[im])++;
+                            // XXX: this pixel ->n increment is wierd
+                            ((psPixels *) ((*questionablePixels)->data[im]))->n = qpPtr->data.S32[im];
+                        }
+                    }
+                }
+
+                //
+                // If the number of rejected pixels is zero, then there's no point
+                // continuing the loop.
+                //
+                if (numRejects == 0) {
+                    break;
+                }
+                psS32 totalRejects = 0;
+                for (psS32 im = 0 ; im < numImages ; im++) {
+                    if (pixelMask->data.U8[im] & maskVal) {
+                        totalRejects++;
+                    }
+                }
+
+                //
+                // XXX: Is it possible to have all pixels rejected?  If so, we should
+                // exit the loop.
+                //
+                if (totalRejects == numImages) {
+                    break;
+                }
+            }
+        }
+        psFree(stdevStats);
+    } else {
+        //
+        // We get here if there is a NULL list of pixels to combine.
+        // Therefore, we combine all pixels in all images.
+        //
+
+        //
+        // Loop over all pixels in all images, set the appropriate data, mask,
+        // error vectors, call psVectorStats(), and set the result in the
+        // combine image.
+        //
+        for (psS32 row = 0 ; row < numRows ; row++) {
+            for (psS32 col = 0 ; col < numCols ; col++) {
+                for (psS32 im = 0 ; im < numImages ; im++) {
+                    // Set the pixel data
+                    pixelData->data.F32[im] =       ((psImage *) images->data[im])->data.F32[row][col];
+
+                    // Set the pixel mask data, if necessary
+                    if (masks != NULL) {
+                        pixelMask->data.U8[im] =   ((psImage *) masks->data[im])->data.F32[row][col];
+                    }
+
+                    // Set the pixel error data, if necessary
+                    if (errors != NULL) {
+                        pixelErrors->data.F32[im] = ((psImage *) errors->data[im])->data.F32[row][col];
+                    }
+
+                }
+                // Combine all the pixels, using the specified stat.
+                stats = psVectorStats(stats, pixelData, pixelErrors, pixelMask, maskVal);
+                combine->data.F32[row][col] = psStatsGetValue(stats, statistic);
+            }
+        }
+    }
+
+    psFree(pixelData);
+    psFree(pixelMask);
+    psFree(pixelErrors);
+    psFree(qpPtr);
+
+    psTrace("psModules.imcombine", 3, "Exiting pmCombineImages(%ld)\n", images->n);
+    return(combine);
+}
+
+
+/******************************************************************************
+XXX: Directly from Paul Price
+ *****************************************************************************/
+static psF32 CalcGradient(
+    psImage *image,
+    psImage *imageMask,
+    psS32 x,
+    psS32 y
+)
+{
+    psTrace("psModules.imcombine", 4, "Calling CalcGradient(%d, %d)\n", x, y);
+    int num = 0;
+    psVector *pixels = psVectorAlloc(8, PS_TYPE_F32); // Array of pixels
+    psVector *mask = psVectorAlloc(8, PS_TYPE_U8); // Corresponding mask
+    pixels->n = 8;
+    mask->n = 8;
+
+    // Get limits
+    int xMin = PS_MAX(x - 1, 0);
+    int xMax = PS_MIN(x + 1, image->numCols - 1);
+    int yMin = PS_MAX(y - 1, 0);
+    int yMax = PS_MIN(y + 1, image->numRows - 1);
+    if (imageMask != NULL) {
+        for (int j = yMin; j <= yMax; j++) {
+            for (int i = xMin; i <= xMax; i++) {
+                if ((i != x) && (j != y) && (0 == imageMask->data.U8[j][i])) {
+                    pixels->data.F32[num] = image->data.F32[j][i];
+                    mask->data.U8[num] = 0;
+                    num++;
+                } else {
+                    mask->data.U8[num] = 1;
+                }
+            }
+        }
+    } else {
+        //
+        // This code is simply the previous loop without the imageMask.
+        // XXX: Consider restructuring this.
+        //
+        for (int j = yMin; j <= yMax; j++) {
+            for (int i = xMin; i <= xMax; i++) {
+                if ((i != x) && (j != y)) {
+                    pixels->data.F32[num] = image->data.F32[j][i];
+                    mask->data.U8[num] = 0;
+                    num++;
+                } else {
+                    mask->data.U8[num] = 1;
+                }
+            }
+        }
+    }
+
+    pixels->n = num;
+    mask->n = num;
+
+    // Get the median
+    psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN);
+    (void)psVectorStats(stats, pixels, NULL, mask, 1);
+    float median = stats->sampleMedian;
+    psFree(stats);
+    psFree(pixels);
+    psFree(mask);
+
+    psTrace("psModules.imcombine", 4, "Exiting CalcGradient(%d, %d)\n", x, y);
+    return(median / image->data.F32[y][x]);
+}
+
+/******************************************************************************
+DetermineRegion(image, myOutToIn): for a psImage and a psPlaneTransform to that
+image, this routine determines the size of the input image which maps to that
+image, and returns the result in a psRegion struct.
+ 
+XXX: Basically, this routine is only guaranteed to work if the transform is
+linear.
+ 
+XXX: Shouldn't this functionality be part of psImageTransform()?
+ *****************************************************************************/
+static psRegion DetermineRegion(psImage *image,
+                                psPlaneTransform *myOutToIn)
+{
+    psTrace("psModules.imcombine", 4, "Calling DetermineRegion()\n");
+    psRegion myRegion;
+    myRegion.x0 = PS_MAX_F32;
+    myRegion.x1 = PS_MIN_F32;
+    myRegion.y0 = PS_MAX_F32;
+    myRegion.y1 = PS_MIN_F32;
+    psPlane in;
+    psPlane out;
+
+    in.x = 0.0;
+    in.y = 0.0;
+
+    psPlaneTransformApply(&out, myOutToIn, &in);
+    if (out.x < myRegion.x0) {
+        myRegion.x0 = out.x;
+    }
+    if (out.x > myRegion.x1) {
+        myRegion.x1 = out.x;
+    }
+    if (out.y < myRegion.y0) {
+        myRegion.y0 = out.y;
+    }
+    if (out.y > myRegion.y1) {
+        myRegion.y1 = out.y;
+    }
+
+    in.x = (psF32) (image->numCols);
+    in.y = 0.0;
+    psPlaneTransformApply(&out, myOutToIn, &in);
+    if (out.x < myRegion.x0) {
+        myRegion.x0 = out.x;
+    }
+    if (out.x > myRegion.x1) {
+        myRegion.x1 = out.x;
+    }
+    if (out.y < myRegion.y0) {
+        myRegion.y0 = out.y;
+    }
+    if (out.y > myRegion.y1) {
+        myRegion.y1 = out.y;
+    }
+
+    in.x = (psF32) (image->numCols);
+    ;
+    in.y = 0.0;
+    psPlaneTransformApply(&out, myOutToIn, &in);
+    if (out.x < myRegion.x0) {
+        myRegion.x0 = out.x;
+    }
+    if (out.x > myRegion.x1) {
+        myRegion.x1 = out.x;
+    }
+    if (out.y < myRegion.y0) {
+        myRegion.y0 = out.y;
+    }
+    if (out.y > myRegion.y1) {
+        myRegion.y1 = out.y;
+    }
+
+    in.x = (psF32) (image->numCols);
+    in.y = (psF32) (image->numRows);
+    psPlaneTransformApply(&out, myOutToIn, &in);
+    if (out.x < myRegion.x0) {
+        myRegion.x0 = out.x;
+    }
+    if (out.x > myRegion.x1) {
+        myRegion.x1 = out.x;
+    }
+    if (out.y < myRegion.y0) {
+        myRegion.y0 = out.y;
+    }
+    if (out.y > myRegion.y1) {
+        myRegion.y1 = out.y;
+    }
+
+    psTrace("psModules.imcombine", 4, "Exiting DetermineRegion()\n");
+    return(myRegion);
+}
+
+/******************************************************************************
+XXX: Don't we have a psLib function for this?
+ *****************************************************************************/
+static psImage *ImageConvertF32(psImage *image)
+{
+    psTrace("psModules.imcombine", 4, "Calling ImageConvertF32()\n");
+    psImage *imgF32 = psImageAlloc(image->numCols, image->numRows, PS_TYPE_F32);
+
+    for (psS32 i = 0 ; i < image->numRows ; i++) {
+        for (psS32 j = 0 ; j < image->numCols ; j++) {
+            imgF32->data.F32[i][j] = (psF32) image->data.U8[i][j];
+        }
+    }
+
+    psTrace("psModules.imcombine", 4, "Exiting ImageConvertF32()\n");
+    return(imgF32);
+}
+
+
+//
+// The following macros define how big the initial pixel list will be, and
+// how much it should be incremented when realloc'ed.
+//
+#define PS_REJECT_PIXEL_INITIAL_PIXEL_LIST_LENGTH 100
+#define PS_REJECT_PIXEL_INITIAL_PIXEL_LIST_LENGTH_INC 100
+/******************************************************************************
+pmRejectPixels(images, errors, inToOut, outToIn, rejThreshold,
+gradLimit)
+ 
+XXX: Optimization: we don't need to transform the entire mask image.
+XXX: The inToOut and outToIn transforms are confusing.  Verify that what
+     I think they mean syncs with PWP.
+ *****************************************************************************/
+psArray *pmRejectPixels(
+    const psArray *images,              ///< Array of input images
+    const psArray *masks,               ///< Array of input image masks
+    const psArray *errors,              ///< The pixels which were rejected in the combination
+    const psArray *inToOut,             ///< Transformation from input to output system
+    const psArray *outToIn,             ///< Transformation from output to input system
+    psF32 rejThreshold,                 ///< Rejection threshold
+    psF32 gradLimit                     ///< Gradient limit
+)
+{
+    psTrace("psModules.imcombine", 3, "Calling pmRejectPixels()\n");
+    PS_ASSERT_PTR_NON_NULL(images, NULL);
+    for (psS32 im = 0 ; im < images->n ; im++) {
+        psImage *tmpImage = (psImage *) images->data[im];
+        PS_ASSERT_IMAGE_NON_NULL(tmpImage, NULL);
+        PS_ASSERT_IMAGE_NON_EMPTY(tmpImage, NULL);
+        PS_ASSERT_IMAGE_TYPE(tmpImage, PS_TYPE_F32, NULL);
+        if (masks != NULL) {
+            PS_ASSERT_INT_EQUAL(images->n, masks->n, NULL);
+            psImage *tmpMask = (psImage *) masks->data[im];
+            PS_ASSERT_IMAGE_NON_NULL(tmpMask, NULL);
+            PS_ASSERT_IMAGE_NON_EMPTY(tmpMask, NULL);
+            PS_ASSERT_IMAGE_TYPE(tmpMask, PS_TYPE_F32, NULL);
+            PS_ASSERT_IMAGES_SIZE_EQUAL(tmpImage, tmpMask, NULL);
+        }
+        PS_ASSERT_IMAGES_SIZE_EQUAL(((psImage *) images->data[0]), tmpImage, NULL);
+    }
+    PS_ASSERT_PTR_NON_NULL(errors, NULL);
+    PS_ASSERT_PTR_NON_NULL(inToOut, NULL);
+    PS_ASSERT_PTR_NON_NULL(outToIn, NULL);
+    // Ensure that the psArray parameters have an element for each image.
+    psS32 numImages = images->n;
+    PS_ASSERT_INT_EQUAL(numImages, errors->n, NULL);
+    PS_ASSERT_INT_EQUAL(numImages, inToOut->n, NULL);
+    PS_ASSERT_INT_EQUAL(numImages, outToIn->n, NULL);
+
+    //
+    // Create the psArray of psPixelLists, one for each image, for rejected pixels.
+    //
+    psArray *rejects = psArrayAlloc(numImages);
+    rejects->n = numImages;
+    for (psS32 im = 0 ; im < numImages ; im++) {
+        rejects->data[im] = (psPtr *) psPixelsAlloc(PS_REJECT_PIXEL_INITIAL_PIXEL_LIST_LENGTH);
+        ((psPixels *)(rejects->data[im]))->n = ((psPixels *)(rejects->data[im]))->nalloc;
+        psPixels *pixels = (psPixels *) rejects->data[im];
+        pixels->n = 0;
+    }
+    //
+    // rPtr is used to maintain a count of the questionable pixels for each image.
+    //
+    psVector *rPtr = psVectorAlloc(numImages, PS_TYPE_S32);
+    psVectorInit(rPtr, 0);
+    rPtr->n = numImages;
+
+    psS32 numCols = ((psImage *) images->data[0])->numCols;
+    psS32 numRows = ((psImage *) images->data[0])->numRows;
+    psRegion myRegion = psRegionSet(0, numCols-1, 0, numRows-1);
+    psU32 maskVal = 1;  // XXX: Is this appropriate?
+
+    psPlane *inCoords = psAlloc(sizeof(psPlane));
+    psPlane *outCoords = psAlloc(sizeof(psPlane));
+
+    for (psS32 im = 0 ; im < numImages ; im++) {
+        //
+        // Extract data from psArrays.
+        //
+        psPixels *pixelList = (psPixels *) errors->data[im];
+
+        psImage *currImage = (psImage *) images->data[im];
+        myRegion.x0 = 0;
+        myRegion.x1 = currImage->numCols;
+        myRegion.y0 = 0;
+        myRegion.y1 = currImage->numRows;
+        psPlaneTransform *myInToOut = (psPlaneTransform *) inToOut->data[im];
+        psPlaneTransform *myOutToIn = (psPlaneTransform *) outToIn->data[im];
+
+        //
+        // Create a psU8 mask image from the list of cosmic pixels.
+        //
+        psImage *maskImage = NULL;
+        maskImage = psPixelsToMask(maskImage, pixelList, myRegion, maskVal);
+        psImage *maskImageF32 = ImageConvertF32(maskImage);
+
+        //
+        // Transform that mask image into detector coordinate space
+        //
+        psRegion myRegionXForm = DetermineRegion(maskImageF32, myOutToIn);
+        psImage *transformedImage = psImageTransform(NULL, NULL, maskImageF32, NULL,
+                                    0, myOutToIn, myRegionXForm, NULL,
+                                    PS_INTERPOLATE_BILINEAR, 0);
+
+        //
+        // Loop over all cosmic pixels.  Transform their coords to detector space.
+        // If the value of the transformed mask is larger than rejThreshold, then
+        // this might be a cosmic ray pixel.  We then calculate the mean gradient
+        // in other images.
+        //
+        for (psS32 p = 0 ; p < pixelList->n ; p++) {
+            inCoords->x = 0.5 + (psF32) (pixelList->data[p]).x;
+            inCoords->y = 0.5 + (psF32) (pixelList->data[p]).y;
+            psPlaneTransformApply(outCoords, myInToOut, inCoords);
+            psF32 maskVal = (psF32) psImagePixelInterpolate(transformedImage, outCoords->x, outCoords->y,
+                            NULL, 0, 0.0, PS_INTERPOLATE_BILINEAR);
+            if (maskVal > rejThreshold) {
+
+                // This is a possible cosmic array pixel.  We must calculate the gradient
+                // at this location in all input images.
+                psF32 meanGrads = 0.0;
+                psS32 numGrads = 0;
+                //
+                // Loop through all other images, calculate their mean gradient.
+                //
+                for (psS32 otherImg = 0 ; otherImg < numImages ; otherImg++) {
+                    if (im != otherImg) {
+                        // Map the outCoords to inCoords that for otherImg space.
+                        psImage *tmpMask = NULL;
+                        if (masks != NULL) {
+                            tmpMask = masks->data[otherImg];
+                        }
+                        psPlaneTransformApply(inCoords,
+                                              (psPlaneTransform * )outToIn->data[otherImg],
+                                              outCoords);
+                        psS32 xPix = (int)(inCoords->x + 0.5);
+                        psS32 yPix = (int)(inCoords->y + 0.5);
+                        if ((xPix >= 0) && (xPix <= ((psImage*)(images->data[otherImg]))->numCols - 1) &&
+                                (yPix >= 0) && (yPix <= ((psImage*)(images->data[otherImg]))->numRows - 1)) {
+                            meanGrads += CalcGradient(images->data[otherImg], tmpMask, xPix, yPix);
+                            numGrads++;
+                        }
+                    }
+                }
+                if (numGrads > 0) {
+                    meanGrads /= (psF32) numGrads;
+                } else {
+                    // XXX: my idea.  Verify with PWP:
+                    meanGrads = 1.0 + gradLimit;
+                }
+
+                // XXX: The SDRS and the prototype code differ significantly here:
+                // if (CalcGradient(inputs->data[i], pixelList->data.x, pixelList->data.y) < (gradLimit * meanGrads)) {
+                if (meanGrads < gradLimit) {
+                    //
+                    // Add this to the list of questionable pixels.  We must ensure that the
+                    // pixelList is large enough; if not, we realloc()
+                    //
+                    psS32 ptr = rPtr->data.S32[im];
+                    psPixels *pixelListPtr = (psPixels *) rejects->data[im];
+                    if (ptr >= pixelListPtr->nalloc) {
+                        rejects->data[im] = (psPtr *) psPixelsRealloc(((psPixels *) rejects->data[im]),
+                                            ((((psPixels *) rejects->data[im])->nalloc) + PS_REJECT_PIXEL_INITIAL_PIXEL_LIST_LENGTH_INC));
+                        // XXX: Can the realloc() fail?  Must we check for NULL?
+                    }
+
+                    ((psPixels *) rejects->data[im])->data[ptr].x = (pixelList->data[p]).x;
+                    ((psPixels *) rejects->data[im])->data[ptr].y = (pixelList->data[p]).y;
+                    (rPtr->data.S32[im])++;
+                    // XXX: this pixel ->n increment is wierd
+                    (((psPixels *) rejects->data[im])->n)++;
+                }
+            }
+        }
+
+        psFree(maskImage);
+        psFree(maskImageF32);
+        psFree(transformedImage);
+    }
+
+    psFree(inCoords);
+    psFree(outCoords);
+    psTrace("psModules.imcombine", 3, "Exiting pmRejectPixels()\n");
+    return(rejects);
+}
Index: /tags/RHL_v1_1/psModules/src/imcombine/pmImageCombine.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/imcombine/pmImageCombine.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/imcombine/pmImageCombine.h	(revision 9533)
@@ -0,0 +1,50 @@
+/** @file  pmImageCombine.h
+ *
+ *  This file will perform image combination of several images of the
+ *  same field, produce a list of questionable pixels, then tag some
+ *  of those pixels as cosmic rays.
+ *
+ *  @author Paul Price, IfA (original prototype)
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-07-28 03:21:19 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#if !defined(PM_IMAGE_COMBINE_H)
+#define PM_IMAGE_COMBINE_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include<stdio.h>
+#include<math.h>
+#include "pslib.h"
+
+psImage *pmCombineImages(
+    psImage *combine,                   ///< Combined image (output)
+    psArray **questionablePixels,       ///< Array of rejection masks
+    const psArray *images,              ///< Array of input images
+    const psArray *errors,              ///< Array of input error images
+    const psArray *masks,               ///< Array of input masks
+    psU32 maskVal,                      ///< Mask value
+    const psPixels *pixels,             ///< Pixels to combine
+    psS32 numIter,                      ///< Number of rejection iterations
+    psF32 sigmaClip,                    ///< Number of standard deviations at which to reject
+    psStats *stats                      ///< Statistics to use in the combination
+);
+
+psArray *pmRejectPixels(
+    const psArray *images,              ///< Array of input images
+    const psArray *masks,               ///< Array of input image masks
+    const psArray *errors,              ///< The pixels which were rejected in the combination
+    const psArray *inToOut,             ///< Transformation from input to output system
+    const psArray *outToIn,             ///< Transformation from output to input system
+    psF32 rejThreshold,                 ///< Rejection threshold
+    psF32 gradLimit                     ///< Gradient limit
+);
+#endif
Index: /tags/RHL_v1_1/psModules/src/imcombine/pmImageSubtract.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/imcombine/pmImageSubtract.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/imcombine/pmImageSubtract.c	(revision 9533)
@@ -0,0 +1,1410 @@
+/** @file  ImageSubtract.c
+ *
+ *  This file will contain code which creates a set of kernel basis
+ *  functions, solves for their solution, and applies them to an image.
+ *
+ *  @author Paul Price, IfA (original prototype)
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-15 09:49:01 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ *   XXX: sync with IfA on this:
+ *   The (x, y) (row, col) issue is becoming a problem.  In this file, and I
+ *   think, the rest of psLib and psModules, the following conventions are used:
+ *
+ * 1) x will correspond to the column, and y will correspond to the row.
+ * 2) When used in function prototypes, the column (and hence x) appears
+ *    first.
+ * 3) When used to index 2-D arrays, obviously, the row (and hence, y)
+ *    appears first. (2 and 3 are the source of confusion).
+ * 4) When (u, v) are used in certain structures.
+ *  u corresponds to x
+ *  v corresponds to y
+ * 5) When element (a, b) is casually referred to in comments, or
+ *    documentation it is unclear where a is the row, or the column.
+ * 6) A convention on loop index variables (i, j) would be convenient.
+ *    Currently, sometimes i corresponds to the column (x),
+ *    usually it corresponds to the row (y).
+ *
+ *  XXX: The following variables are used an interpreted this way:
+ * kernelSize: Means that the actual kernel is a square (1 + 2 * kernelSize) per side.
+ * border: When accessing an image, a swath of pixels this wide is ignored.
+ * footprint: When accessing a stample, a square of pixels, footprint pixels per side,
+ *  are looked at.  We must ensure that (footprint+kernelSize) pixels exist
+ *  around the center.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <pslib.h>
+#include "pmImageSubtract.h"
+
+/*******************************************************************************
+    Private alloc/free functions.
+XXX: It's not clear if the SubtractionKernels alloc/free functions are feasable.
+ ******************************************************************************/
+void p_pmStampFree(pmStamp *stamp)
+{
+    psFree(stamp->matrix);
+    psFree(stamp->vector);
+}
+
+pmStamp *p_pmStampAlloc(pmStampStatus status)
+{
+    pmStamp *stamp = (pmStamp*)psAlloc(sizeof(pmStamp));
+    stamp->x = 0;
+    stamp->p_xSize = 0;
+    stamp->y = 0;
+    stamp->p_ySize = 0;
+    stamp->matrix = NULL;
+    stamp->vector = NULL;
+    stamp->status = status;
+
+    psMemSetDeallocator(stamp, (psFreeFunc)p_pmStampFree);
+
+    return(stamp);
+}
+
+void p_pmSubtractionKernelsFree(psSubtractionKernels *kernels)
+{
+    psFree(kernels->u);
+    psFree(kernels->v);
+    psFree(kernels->sigma);
+    psFree(kernels->xOrder);
+    psFree(kernels->yOrder);
+    psFree(kernels->preCalc);
+
+    psFree(kernels);
+}
+
+psSubtractionKernels *p_pmSubtractionKernelsAlloc(int numBasisFunctions,
+        pmSubtractionKernelsType type)
+{
+    psSubtractionKernels *tmp = (psSubtractionKernels *) psAlloc(sizeof(psSubtractionKernels));
+
+    tmp->type = type;
+    psMemSetDeallocator(tmp, (psFreeFunc) p_pmSubtractionKernelsFree);
+    return(tmp);
+}
+
+/*******************************************************************************
+psSubtractionKernels struct.
+ ******************************************************************************/
+psSubtractionKernels *pmSubtractionKernelsAllocPOIS(int size,
+        int spatialOrder)
+{
+    psTrace("psModules.imcombine", 3,
+            "Calling pmSubtractionKernelsAllocPOIS(%d, %d)\n", size, spatialOrder);
+    PS_ASSERT_INT_POSITIVE(size, NULL);
+    PS_ASSERT_INT_POSITIVE(spatialOrder, NULL);
+    //
+    // Calculate the number of basis functions (nBF)
+    //
+    psS32 xKernelHalfSize = size;
+    psS32 yKernelHalfSize = size;
+    psS32 nBF = (2 * xKernelHalfSize + 1) *
+                (2 * yKernelHalfSize + 1) *
+                (spatialOrder + 1) *
+                (spatialOrder + 2) / 2;
+
+    //
+    // Generate the new psSubtractionKernels data structure:
+    //
+    psSubtractionKernels *tmp = (psSubtractionKernels *) psAlloc(sizeof(psSubtractionKernels));
+    tmp->type = PM_SUBTRACTION_KERNEL_POIS;
+    tmp->u = psVectorAlloc(nBF, PS_TYPE_F32);
+    tmp->v = psVectorAlloc(nBF, PS_TYPE_F32);
+    tmp->u->n = tmp->u->nalloc;
+    tmp->v->n = tmp->v->nalloc;
+    tmp->sigma = NULL;
+    tmp->xOrder = psVectorAlloc(nBF, PS_TYPE_F32);
+    tmp->xOrder->n = tmp->xOrder->nalloc;
+    tmp->yOrder = psVectorAlloc(nBF, PS_TYPE_F32);
+    tmp->yOrder->n = tmp->yOrder->nalloc;
+    tmp->subIndex = 0;
+    tmp->preCalc = NULL;
+    tmp->size = size;
+    tmp->spatialOrder = spatialOrder;
+
+    //
+    // This corresponds to the Kernel Basis Function with (u, v) = (0, 0)
+    //
+    // Put the (u,v) = (0,0) component right at the start of the list
+    // for convenience.
+    //
+    psS32 ptr = 0;
+    for (psS32 order = 0; order <= spatialOrder; order++) {
+        for (psS32 xOrder = 0; xOrder <= order; xOrder++) {
+            psS32 yOrder = order - xOrder;
+            tmp->u->data.F32[ptr] = 0;
+            tmp->v->data.F32[ptr] = 0;
+            tmp->xOrder->data.F32[ptr] = xOrder;
+            tmp->yOrder->data.F32[ptr] = yOrder;
+            ptr++;
+        }
+    }
+
+    //
+    // Iterate over (u,v).  Generate a set of kernels for each (u, v).
+    //
+    for (psS32 v = -yKernelHalfSize; v <= yKernelHalfSize; v++) {
+        for (psS32 u = -xKernelHalfSize; u <= xKernelHalfSize; u++) {
+            // Already did (u,v) = (0,0): it's at the start, so skip it now.
+            if ((u != 0) || (v != 0)) {
+                //
+                // Iterate over spatial order.  This loop creates the terms for
+                // x^xOrder * y^yOrder  such that (xOrder+yOrder) <= spatialOrder.
+                //
+                for (psS32 order = 0; order <= spatialOrder; order++) {
+                    for (psS32 xOrder = 0; xOrder <= order; xOrder++) {
+                        psS32 yOrder = order - xOrder;
+                        tmp->u->data.F32[ptr] = u;
+                        tmp->v->data.F32[ptr] = v;
+                        tmp->xOrder->data.F32[ptr] = xOrder;
+                        tmp->yOrder->data.F32[ptr] = yOrder;
+                        ptr++;
+                    }
+                }
+            }
+        }
+    }
+
+    psTrace("psModules.imcombine", 3,
+            "Exiting pmSubtractionKernelsAllocPOIS(%d, %d)\n", size, spatialOrder);
+    return(tmp);
+}
+
+/*******************************************************************************
+XXX: Get the types correct (u, v, xOrder, yOrder).
+ 
+XXX: Code review this.
+ ******************************************************************************/
+psSubtractionKernels *pmSubtractionKernelsAllocISIS(const psVector *sigmas,
+        const psVector *orders,
+        int size,
+        int spatialOrder)
+{
+    PS_ASSERT_VECTOR_NON_NULL(sigmas, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(orders, NULL);
+    psTrace("psModules.imcombine", 3,
+            "Calling pmSubtractionKernelsAllocISIS(%ld, %ld, %d, %d)\n",
+            sigmas->n, orders->n, size, spatialOrder);
+    PS_ASSERT_INT_POSITIVE(size, NULL);
+    PS_ASSERT_INT_POSITIVE(spatialOrder, NULL);
+    PS_ASSERT_VECTOR_TYPE(sigmas, PS_TYPE_F32, NULL);
+    PS_ASSERT_VECTOR_TYPE(orders, PS_TYPE_S32, NULL);
+    //
+    // Calculate the number of basis functions (nBF).
+    //
+    psS32 numSigmas = sigmas->n;
+
+    // XXX: Get rid of the sigma loop?  We merely multiple nBF by numSigmas?
+    // XXX: Verify that all this is correct.
+    psS32 nBF = 0;
+    for (psS32 s = 0 ; s < numSigmas ; s++) {
+        for (psS32 o = 0 ; o < orders->n ; o++) {
+            nBF+=((orders->data.S32[o] + 1) * (orders->data.S32[o] + 2) / 2);
+        }
+    }
+    nBF*= ((spatialOrder + 1) * (spatialOrder + 2) / 2);
+
+    //
+    // Generate the new psSubtractionKernels data structure:
+    //
+    psSubtractionKernels *tmp = (psSubtractionKernels *) psAlloc(sizeof(psSubtractionKernels));
+    tmp->type = PM_SUBTRACTION_KERNEL_ISIS;
+    tmp->u = psVectorAlloc(nBF, PS_TYPE_F32);
+    tmp->v = psVectorAlloc(nBF, PS_TYPE_F32);
+    tmp->u->n = tmp->u->nalloc;
+    tmp->v->n = tmp->v->nalloc;
+    tmp->sigma = psVectorAlloc(nBF, PS_TYPE_F32);
+    tmp->xOrder = psVectorAlloc(nBF, PS_TYPE_F32);
+    tmp->yOrder = psVectorAlloc(nBF, PS_TYPE_F32);
+    tmp->subIndex = 0;
+    tmp->size = size;
+    tmp->spatialOrder = spatialOrder;
+    tmp->preCalc = psArrayAlloc(nBF);
+    tmp->u->n = tmp->u->nalloc;
+    tmp->v->n = tmp->v->nalloc;
+    tmp->sigma->n = tmp->sigma->nalloc;
+    tmp->xOrder->n = tmp->xOrder->nalloc;
+    tmp->yOrder->n = tmp->yOrder->nalloc;
+    tmp->preCalc->n = tmp->preCalc->nalloc;
+
+    //
+    // We loop through all combinations of sigmas and polynomial orders
+    // creating the kernel basis functions.
+    //
+    psS32 ptr = 0;
+    for (psS32 sigPtr = 0 ; sigPtr < numSigmas ; sigPtr++) {
+        tmp->sigma->data.F32[sigPtr] = sigmas->data.F32[sigPtr];
+        //
+        // (xOrderP, yOrderP) are the order of the polynomial that modify the
+        // gaussian in the kernel.  They correspond to (j, k) in equation (5)
+        // from the psModules SDRS.
+        //
+        for (psS32 o = 0 ; o < orders->n ; o++) {
+            for (psS32 orderP = 0 ; orderP <= orders->data.S32[o] ; orderP++) {
+                for (psS32 xOrderP = 0 ; xOrderP <= orderP ; xOrderP++) {
+                    psS32 yOrderP = orderP - xOrderP;
+
+                    psImage *currPreCalc = psImageAlloc(1 + (2 * size), 1 + (2 * size), PS_TYPE_F32);
+                    psImageInit(currPreCalc, 0.0);
+                    psBool setPreCalc = true;
+                    //
+                    // We loop through all spatial orders.  Since they have no effect on
+                    // the preCalc images, we only calculate them once, and store pointers
+                    // in tmp->preCalc->data[ptr] for other spatial orders.
+                    //
+                    for (psS32 order = 0; order <= spatialOrder; order++) {
+                        for (psS32 orderXTerm = 0; orderXTerm <= order; orderXTerm++) {
+                            PS_ASSERT_INT_LESS_THAN(ptr, nBF, NULL);
+
+                            psS32 orderYTerm = order - orderXTerm;
+
+                            tmp->u->data.F32[ptr] = xOrderP;
+                            tmp->v->data.F32[ptr] = yOrderP;
+                            tmp->xOrder->data.F32[ptr] = orderXTerm;
+                            tmp->yOrder->data.F32[ptr] = orderYTerm;
+                            tmp->sigma->data.F32[ptr] = sigmas->data.F32[sigPtr];
+                            tmp->preCalc->data[ptr] = (psPtr *) currPreCalc;
+
+                            //
+                            // We calculate the preCalc image only the first time through
+                            // this loop.  Otherwise, we increment the memory reference
+                            // counter.
+                            //
+                            if (setPreCalc == true) {
+                                for (psS32 v = -size; v <= size; v++) {
+                                    for (psS32 u = -size; u <= size; u++) {
+                                        // Scale the (u,v) coordinates in kernel space to [-1.0:1.0].
+                                        psF32 uScaled = ((psF32) u) / ((psF32) size);
+                                        psF32 vScaled = ((psF32) v) / ((psF32) size);
+
+                                        // Compute the value of the kernel at location (u, v):
+                                        psF32 exponent = (PS_SQR(uScaled) + PS_SQR(vScaled)) /
+                                                         (2.0 * PS_SQR(sigmas->data.F32[sigPtr]));
+                                        currPreCalc->data.F32[v+size][u+size] =
+                                            exp(-exponent) *
+                                            pow(uScaled, orderXTerm) *
+                                            pow(vScaled, orderYTerm);
+                                    }
+                                }
+                                setPreCalc = false;
+                            } else {
+                                psMemIncrRefCounter(currPreCalc);
+                            }
+                            ptr++;
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    psTrace("psModules.imcombine", 3,
+            "Exiting pmSubtractionKernelsAllocISIS(%ld, %ld, %d, %d)\n",
+            sigmas->n, orders->n, size, spatialOrder);
+    return(tmp);
+}
+
+/*******************************************************************************
+pmSubtractionFindStamps(stamps, image, mask, maskVal, threshold, xNum, yNum, border)
+ 
+XXX: The SDRS and the proptotype code differ significantly.
+ Prototype: When a maximum pixel is found within a stamp, an area of size
+     2*footprint is searched around that pixel looking for masked pixels.
+ SDRS: none of that is required.
+ 
+XXX: Do we need to care about case where yNum/xNum does not evenly divide the
+nnumber of rows/columns in the image?
+ ******************************************************************************/
+psArray *pmSubtractionFindStamps(psArray *stamps,        ///< Output stamps, or NULL
+                                 const psImage *image,   ///< Image for which to find stamps
+                                 const psImage *mask,    ///< Mask
+                                 psU32 maskVal,          ///< Value for mask
+                                 psF32 threshold,        ///< Threshold for stamps in the image
+                                 psS32 xNum,             ///< Number of stamps in x
+                                 psS32 yNum,             ///< Number of stamps in y
+                                 psS32 border            ///< Border around image to ignore (should be size of kernel or larger)
+                                )
+{
+    psTrace("psModules.imcombine", 3,
+            "Calling pmSubtractionFindStamps(%d, %f, %d, %d, %d)\n",
+            maskVal, threshold, xNum, yNum, border);
+    PS_ASSERT_IMAGE_NON_NULL(image, NULL);
+    PS_ASSERT_IMAGE_NON_EMPTY(image, NULL);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, NULL);
+    if (mask != NULL) {
+        PS_ASSERT_IMAGES_SIZE_EQUAL(image, mask, NULL);
+        PS_ASSERT_IMAGE_TYPE(mask, PS_TYPE_U8, NULL);
+    }
+    PS_ASSERT_INT_POSITIVE(xNum, NULL);
+    PS_ASSERT_INT_POSITIVE(yNum, NULL);
+    PS_ASSERT_INT_POSITIVE(border, NULL);
+    PS_ASSERT_INT_LARGER_THAN(image->numCols, xNum, NULL);
+    PS_ASSERT_INT_LARGER_THAN(image->numRows, yNum, NULL);
+    PS_ASSERT_INT_LARGER_THAN(image->numCols, (2 * border), NULL);
+    PS_ASSERT_INT_LARGER_THAN(image->numRows, (2 * border), NULL);
+
+    if (stamps != NULL) {
+        PS_ASSERT_INT_EQUAL(stamps->n, (xNum * yNum), NULL);
+        //
+        // Ensure that a pmStamp struct exists at each psArray location.
+        //
+        for (psS32 s = 0 ; s < (xNum * yNum) ; s++) {
+            if (NULL == stamps->data[s]) {
+                stamps->data[s] = (psPtr *) p_pmStampAlloc(PM_STAMP_REJECTED);
+            }
+        }
+    } else {
+        stamps = (psArray *) psArrayAlloc(xNum * yNum);
+        stamps->n = stamps->nalloc;
+        for (psS32 s = 0 ; s < (xNum * yNum) ; s++) {
+            stamps->data[s] = (psPtr *) p_pmStampAlloc(PM_STAMP_REJECTED);
+        }
+    }
+    psS32 numRows = image->numRows;
+    psS32 numCols = image->numCols;
+
+    //
+    // Iterate over the image sections
+    //
+    // XXX: Must handle cases where image size is not an even multiple of xNum or yNum
+    // they are currently ignored.
+    //
+    psS32 num = 0;
+    for (psS32 j = 0; j < yNum; j++) {
+        for (psS32 i = 0; i < xNum; i++) {
+            pmStamp *stamp = (pmStamp *) stamps->data[num];
+            //
+            // Only find a new stamp if we need to
+            //
+            if (stamp->status == PM_STAMP_REJECTED) {
+                //
+                // Find maximum non-masked value in the image section,
+                // but don't include a footprint around the edge
+                //
+                psF32 max = -INFINITY;
+                psS32 bestx = 0;
+                psS32 besty = 0;
+                //
+                // The following nested loop iterates over every pixel in the mask
+                // associated with this (i, j).  It ignores pixels within a
+                // border of pixels from the image edge.
+                //
+                // XXX: verify (numX, numY), then get rid of it.
+                //
+                psS32 numX = xNum;
+                psS32 numY = yNum;
+                psS32 yMin = border + j * (numCols - 2.0 * border) / numY;
+                psS32 yMax = (border + (j + 1) * (numCols - 2.0 * border) / numY) - 1;
+                psS32 xMin = border + i * (numRows - 2.0 * border) / numX;
+                psS32 xMax = (border + (i + 1) * (numRows - 2.0 * border) / numX) - 1;
+
+                if ((yMax >= image->numRows) ||
+                        (xMax >= image->numCols) ||
+                        (yMin < 0) ||
+                        (xMin < 0)) {
+                    // XXX: We skip this stamp since its borders extends beyond the image.
+                    // XXX: This is here mainly as a safeguard.  We need to redefine the above
+                    // min/max pixels calculation to ensure that all stamps are legitimate.
+
+                    stamp->x = -1;
+                    stamp->y = -1;
+                    stamp->status = PM_STAMP_NONE;
+                } else {
+                    stamp->p_xSize = 1 + (xMax - xMin);
+                    stamp->p_ySize = 1 + (yMax - yMin);
+                    stamp->p_xMin = xMin;
+                    stamp->p_xMax = xMax;
+                    stamp->p_yMin = yMin;
+                    stamp->p_yMax = yMax;
+
+                    for (psS32 y = yMin; y <= yMax ; y++) {
+                        for (psS32 x = xMin; x <= xMax ; x++) {
+                            // Determine if this pixel is larger than the max, and unmasked.
+                            if (image->data.F32[y][x] > max) {
+                                if ((mask == NULL) || !((mask->data.U8[y][x]) & maskVal)) {
+                                    max = image->data.F32[y][x];
+                                    bestx = x;
+                                    besty = y;
+                                }
+                            }
+                        }
+                    }
+
+                    //
+                    // If the max pixel is larger than the threshold, we keep this stamp.
+                    // Otherwise, mark the stamp as PM_STAMP_NONE
+                    //
+                    if (image->data.F32[besty][bestx] >= threshold) {
+                        stamp->x = bestx;
+                        stamp->y = besty;
+                        stamp->status = PM_STAMP_RECALC;
+                    } else {
+                        stamp->x = bestx;
+                        stamp->y = besty;
+                        stamp->status = PM_STAMP_NONE;
+                    }
+                }
+            }
+            num++;
+        }
+    }
+    psTrace("psModules.imcombine", 3,
+            "Exiting pmSubtractionFindStamps(%d, %f, %d, %d, %d)\n",
+            maskVal, threshold, xNum, yNum, border);
+    return(stamps);
+}
+
+/*******************************************************************************
+GenSpatialOrder(spatialOrder, x, y): generates and returns a psImage in which
+the [i][j] location is calculated as (x^i * y^j).
+ 
+XXX: Modify loop so that terms higher than spatialOrder are not computed.
+ 
+XXX: Modify this so that [i][j] location is calculated as (x^j * y^i)?
+ ******************************************************************************/
+static psImage *GenSpatialOrder(psS32 spatialOrder,
+                                psF32 x,
+                                psF32 y)
+{
+    psTrace("psModules.imcombine", 4,
+            "Calling GenSpatialOrder(%d, %f, %f)\n", spatialOrder, x, y);
+
+    psImage *polyValues = psImageAlloc(spatialOrder+1, spatialOrder+1, PS_TYPE_F64);
+
+    psF64 xSum = 1.0;
+    psF64 ySum = 1.0;
+    for (psS32 i = 0; i < spatialOrder + 1; i++) {
+        ySum = xSum;
+        for (psS32 j = 0; j < spatialOrder + 1; j++) {
+            polyValues->data.F64[i][j] = ySum;
+            ySum*= y;
+        }
+        xSum*= x;
+    }
+
+    psTrace("psModules.imcombine", 4,
+            "Exiting GenSpatialOrder(%d, %f, %f)\n", spatialOrder, x, y);
+
+    return(polyValues);
+}
+
+
+/*******************************************************************************
+IsisKernelConvolve(input, kernels, kernelID, col, row): This routine
+convolves a single kernel basis function with a pixel in an image.
+  ******************************************************************************/
+static psF32 IsisKernelConvolve(const psImage *input,
+                                const psSubtractionKernels *kernels,
+                                psS32 kernelID,
+                                psS32 col,
+                                psS32 row)
+{
+
+
+    psTrace("psModules.imcombine", 4,
+            "Calling IsisKernelConvolve(%d, %d, %d)\n", kernelID, col, row);
+    psS32 spatialOrder = kernels->spatialOrder;
+    psS32 kernelSize = kernels->size;
+    psS32 xOrder = (psS32) kernels->xOrder->data.F32[kernelID];
+    psS32 yOrder = (psS32) kernels->yOrder->data.F32[kernelID];
+    psF32 numColsHalf = 0.5 * (psF32) input->numCols;
+    psF32 numRowsHalf = 0.5 * (psF32) input->numRows;
+    psF32 imageX = (((psF32) col) - numColsHalf) / numColsHalf; // Normalised position
+    psF32 imageY = (((psF32) row) - numRowsHalf) / numRowsHalf; // Normalised position
+
+    psImage *polyValues = GenSpatialOrder(spatialOrder, imageX, imageY);
+
+    psF64 polyVal = polyValues->data.F64[yOrder][xOrder];
+
+    psImage *preCalc = (psImage *) kernels->preCalc->data[kernelID];
+
+    // XXX: Are the following asserts really necessary?
+    PS_ASSERT_INT_LARGER_THAN_OR_EQUAL(row-kernelSize, 0, NAN);
+    PS_ASSERT_INT_LESS_THAN(row+kernelSize, input->numRows, NAN);
+    PS_ASSERT_INT_LARGER_THAN_OR_EQUAL(col-kernelSize, 0, NAN);
+    PS_ASSERT_INT_LESS_THAN(col+kernelSize, input->numCols, NAN);
+    psF32 conv = 0.0;
+    for (psS32 yy = -kernelSize ; yy < kernelSize ; yy++) {
+        for (psS32 xx = -kernelSize ; xx < kernelSize ; xx++) {
+            conv += input->data.F32[yy+row][xx+col] *
+                    preCalc->data.F32[yy+kernelSize][xx+kernelSize] *
+                    polyVal;
+        }
+    }
+    psFree(polyValues);
+
+    psTrace("psModules.imcombine", 4,
+            "Exiting IsisKernelConvolve(%d, %d, %d)\n", kernelID, col, row);
+    return(conv);
+}
+
+/*******************************************************************************
+ConvolvePixelPois(input, mask, badStampMaskVal, solution, kernels, col, row):
+ 
+This routine takes a single pixel in the psImage input and convolves it with
+the set of kernel basis functions and their appropriate weights in solution.
+It returns the value of the convolved pixel.
+ 
+XXX: Static structure for polyValues?
+ ******************************************************************************/
+static psF32 ConvolvePixelPois(const psImage *input,
+                               const psImage *mask,
+                               psU32 badStampMaskVal,
+                               const psVector *solution,
+                               const psSubtractionKernels *kernels,
+                               psS32 col,
+                               psS32 row)
+{
+    psTrace("psModules.imcombine", 4,
+            "Calling ConvolvePixelPois(%d, %d)\n", col, row);
+    psS32 nBF = kernels->u->n;
+    psF32 numColsHalf = 0.5 * (psF32) input->numCols;
+    psF32 numRowsHalf = 0.5 * (psF32) input->numRows;
+    psF32 background = solution->data.F64[solution->n-1];
+    psS32 spatialOrder = kernels->spatialOrder;
+    psF32 conv = background; // Initial convolved value
+
+    if ((mask == NULL) || !(mask->data.U8[row][col] & badStampMaskVal)) {
+        psF32 imageX = (((psF32) col) - numColsHalf) / numColsHalf; // Normalised position
+        psF32 imageY = (((psF32) row) - numRowsHalf) / numRowsHalf; // Normalised position
+        psImage *polyValues = GenSpatialOrder(spatialOrder, imageX, imageY);
+
+        // Iterate over the kernel basis functions
+        for (psS32 k = 0; k < nBF; k++) {
+            psS32 u = (psS32) kernels->u->data.F32[k];
+            psS32 v = (psS32) kernels->v->data.F32[k];
+
+            // XXX: What's the story with this?
+            #if 0
+
+            psS32 xOrder = (psS32) kernels->xOrder->data.F32[k];
+            psS32 yOrder = (psS32) kernels->yOrder->data.F32[k];
+            psF64 polyVal = polyValues->data.F64[yOrder][xOrder];
+            #else
+
+            psF32 polyVal = 1.0;
+            #endif
+
+            // XXX: Why this?
+            if (k == 0) {
+                conv += solution->data.F64[k] * input->data.F32[row - v][col - u] * polyVal;
+            } else {
+                conv += solution->data.F64[k] *
+                        (input->data.F32[row - v][col - u] * polyVal - input->data.F32[row][col]);
+            }
+        }
+        psFree(polyValues);
+    }
+
+    psTrace("psModules.imcombine", 4,
+            "Exiting ConvolvePixelPois(%d, %d)\n", col, row);
+    return(conv);
+}
+
+
+
+/*******************************************************************************
+ConvolvePixelIsis(input, mask, badStampMaskVal, solution, kernels, col, row):
+ 
+This routine takes a single pixel in the psImage input and convolves it with
+the set of kernel basis functions and their appropriate weights in solution.
+It returns the value of the convolved pixel.
+ 
+XXX: Static structure for polyValues?
+ ******************************************************************************/
+static psF32 ConvolvePixelIsis(const psImage *input,
+                               const psImage *mask,
+                               psU32 badStampMaskVal,
+                               const psVector *solution,
+                               const psSubtractionKernels *kernels,
+                               psS32 col,
+                               psS32 row)
+{
+    psTrace("psModules.imcombine", 4,
+            "Calling ConvolvePixelIsis(%d, %d)\n", col, row);
+    psF32 background = solution->data.F64[solution->n-1];
+    psF32 conv = background; // Initial convolved value
+
+    if ((mask == NULL) || !(mask->data.U8[row][col] & badStampMaskVal)) {
+        // Iterate over the kernel basis functions
+        for (psS32 k = 0; k < kernels->u->n; k++) {
+            conv += IsisKernelConvolve(input, kernels, k, col, row);
+        }
+    }
+
+    psTrace("psModules.imcombine", 4,
+            "Exiting ConvolvePixelIsis(%d, %d)\n", col, row);
+    return(conv);
+}
+
+/*******************************************************************************
+ConvolveImage(input, mask, badStampMaskVal, solution, kernels): convolves an
+arbitrary image with either an ISIS or POIS set of kernel basis functions.
+ ******************************************************************************/
+static psImage *ConvolveImage(const psImage *input,
+                              const psImage *mask,
+                              psU32 badStampMaskVal,
+                              const psVector *solution,
+                              const psSubtractionKernels *kernels)
+{
+    psTrace("psModules.imcombine", 4, "Calling ConvolveImage()\n");
+    PS_ASSERT_IMAGE_NON_NULL(input, NULL);
+    PS_ASSERT_IMAGE_NON_EMPTY(input, NULL);
+    PS_ASSERT_IMAGE_TYPE(input, PS_TYPE_F32, NULL);
+    if (mask != NULL) {
+        PS_ASSERT_IMAGES_SIZE_EQUAL(input, mask, NULL);
+        PS_ASSERT_IMAGE_TYPE(mask, PS_TYPE_U8, NULL);
+    }
+    PS_ASSERT_VECTOR_NON_NULL(solution, NULL);
+    PS_ASSERT_VECTOR_TYPE(solution, PS_TYPE_F64, NULL);
+    PS_ASSERT_PTR_NON_NULL(kernels, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->v, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->xOrder, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->yOrder, NULL);
+    if (kernels->preCalc != NULL) {
+        PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->preCalc, NULL);
+    } else {
+        if (kernels->type != PM_SUBTRACTION_KERNEL_POIS) {
+            psError(PS_ERR_BAD_PARAMETER_NULL, true,
+                    "Unallowable operation: kernels->preCalc == NULL and kernels->type != PM_SUBTRACTION_KERNEL_POIS.\n");
+            return(NULL);
+        }
+    }
+    psS32 nBF = kernels->u->n;
+    PS_ASSERT_VECTOR_SIZE(solution, (long)nBF+1, NULL);
+
+    psS32 numCols = input->numCols;
+    psS32 numRows = input->numRows;
+    psS32 kernelSize = kernels->size;
+
+    psImage *convolved = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+
+    for (psS32 y = kernelSize; y < numRows - kernelSize; y++) {
+        for (psS32 x = kernelSize; x < numCols - kernelSize; x++) {
+            if (kernels->type == PM_SUBTRACTION_KERNEL_POIS) {
+                convolved->data.F32[y][x] = ConvolvePixelPois(input, mask, badStampMaskVal,
+                                            solution, kernels, x, y);
+            } else if (kernels->type == PM_SUBTRACTION_KERNEL_ISIS) {
+                convolved->data.F32[y][x] = ConvolvePixelIsis(input, mask, badStampMaskVal,
+                                            solution, kernels, x, y);
+            } else {
+                psLogMsg(__func__, PS_LOG_WARN, "WARNING: unknown kernel type.  Returning NULL\n");
+                return(NULL);
+            }
+        }
+    }
+
+    //
+    // Pad the rest of the convolved image with 0.0
+    //
+    for (psS32 y = kernelSize; y < numRows - kernelSize; y++) {
+        for (psS32 x = 0; x < kernelSize; x++) {
+            convolved->data.F32[y][x] = 0.0;
+        }
+        for (psS32 x = numCols - kernelSize; x < numCols; x++) {
+            convolved->data.F32[y][x] = 0.0;
+        }
+    }
+    for (psS32 y = 0; y < kernelSize; y++) {
+        for (psS32 x = 0; x < numCols; x++) {
+            convolved->data.F32[y][x] = 0.0;
+        }
+    }
+    for (psS32 y = numRows - kernelSize; y < numRows; y++) {
+        for (psS32 x = 0; x < numCols; x++) {
+            convolved->data.F32[y][x] = 0.0;
+        }
+    }
+
+    psTrace("psModules.imcombine", 4, "Exiting ConvolveImage()\n");
+    return convolved;
+}
+
+
+
+
+
+/*******************************************************************************
+XXX: We should assert that the (footprint, kernelSize, imageSize) stuff
+ensures that all data is accessed in bounds?
+ ******************************************************************************/
+bool pmSubtractionCalculateEquation(psArray *stamps,          ///< The stamps for which to calculate the equation,
+                                    const psImage *reference, ///< Reference image
+                                    const psImage *input,     ///< Input image
+                                    const psSubtractionKernels *kernels, ///< The kernel basis functions
+                                    psS32 footprint           ///< Half-size of region over which to calculate equation
+                                   )
+{
+    psTrace("psModules.imcombine", 3,
+            "Calling pmSubtractionCalculateEquation()\n");
+    PS_ASSERT_PTR_NON_NULL(stamps, false);
+    PS_ASSERT_IMAGE_NON_NULL(reference, false);
+    PS_ASSERT_IMAGE_NON_EMPTY(reference, false);
+    PS_ASSERT_IMAGE_NON_NULL(input, false);
+    PS_ASSERT_IMAGE_NON_EMPTY(input, false);
+    PS_ASSERT_IMAGES_SIZE_EQUAL(reference, input, false);
+    PS_ASSERT_PTR_NON_NULL(kernels, false);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->v, false);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->xOrder, false);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->yOrder, false);
+    if (kernels->type == PM_SUBTRACTION_KERNEL_ISIS) {
+        PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->preCalc, false);
+    }
+    psS32 kernelSize = kernels->size;
+    PS_ASSERT_INT_NONNEGATIVE(footprint, false);
+    //
+    // For each legitimate stamp, ensure that the footprint is small enough to perform
+    // the full calculation.
+    //
+    // XXX: Verify with IfA that this is a reasonable action.
+    //
+    for (psS32 s = 0; s < stamps->n; s++) {
+        pmStamp *stamp = (pmStamp *) stamps->data[s];
+        if (stamp->status == PM_STAMP_RECALC) {
+            // XXX: trace message
+            // printf("stamp %d (x, y) is (%d, %d).  Footprint is %d.  kernelSize is %d.\n", s, stamp->x, stamp->y, footprint, kernelSize);
+            if (((stamp->y - (footprint + kernelSize)) < 0) ||
+                    ((stamp->x - (footprint + kernelSize)) < 0) ||
+                    ((stamp->y + footprint + kernelSize) >= input->numRows) ||
+                    ((stamp->x + footprint + kernelSize) >= input->numCols)) {
+                stamp->status = PM_STAMP_NONE;
+                psLogMsg(__func__, PS_LOG_WARN,
+                         "WARNING: stamp %d will be ignored.  It exceeds image size: access columns (%d to %d) and rows (%d to %d)\n",
+                         s,
+                         stamp->x - (footprint + kernelSize),
+                         (stamp->x + footprint + kernelSize) - 1,
+                         stamp->y - (footprint + kernelSize),
+                         (stamp->y + footprint + kernelSize) - 1);
+            }
+        }
+    }
+
+    psS32 numHalfRows = reference->numRows;
+    psS32 numHalfCols = reference->numCols;
+    psS32 spatialOrder = kernels->spatialOrder;
+
+    //
+    // The numSolveParams incorporates the additional parameter for the
+    // background value, which we must solve for.
+    //
+    psS32 numKernels = kernels->u->n;
+    int numSolveParams = numKernels + 1;
+    int bgIndex = numKernels;        // Index in matrix for the background
+
+    //
+    // We iterate over each stamp, allocate the matrix and vectors if
+    // necessary, and then calculate those matrix/vectors.
+    //
+    for (psS32 s = 0; s < stamps->n; s++) {
+        pmStamp *stamp = (pmStamp *) stamps->data[s];
+        psTrace("psModules.imcombine", 6, "subCalcEqn(): stamp %d\n", s);
+        if (stamp->status == PM_STAMP_RECALC) {
+            psTrace("psModules.imcombine", 6, "subCalcEqn(): stamp %d: status is PM_STAMP_RECALC.\n", s);
+            psImage *stampMatrix = stamp->matrix;
+            psVector *stampVector = stamp->vector;
+
+            if (stampMatrix == NULL) {
+                stampMatrix = psImageAlloc(numSolveParams, numSolveParams, PS_TYPE_F64);
+                stamp->matrix = stampMatrix;
+            } else {
+                PS_ASSERT_IMAGE_TYPE(stampMatrix, PS_TYPE_F64, false);
+                PS_ASSERT_IMAGE_SIZE(stampMatrix, numSolveParams, numSolveParams, false);
+            }
+            psImageInit(stampMatrix, 0.0);
+
+            if (stampVector == NULL) {
+                stampVector = psVectorAlloc(numSolveParams, PS_TYPE_F64);
+                stampVector->n = stampVector->nalloc;
+                stamp->vector = stampVector;
+            } else {
+                PS_ASSERT_VECTOR_TYPE(stampVector, PS_TYPE_F64, false);
+                PS_ASSERT_VECTOR_SIZE(stampVector, (long)numSolveParams, false);
+            }
+            psVectorInit(stampVector, 0.0);
+            psTrace("psModules.imcombine", 6, "subCalcEqn(): stamp %d: allocate matrix and vector.\n", s);
+
+            //
+            // Evaluate the spatial-order polynomial.  The [i][j]-th element of
+            // the psImage polyValues will hold (x^i * y^j) for the stamp.  The
+            // (x, y) value are scaled to [-1:1]
+            //
+            psImage *polyValues = GenSpatialOrder(spatialOrder,
+                                                  ((psF64) (stamp->x - numHalfCols)) / ((psF64) numHalfCols),
+                                                  ((psF64) (stamp->y - numHalfRows)) / ((psF64) numHalfRows));
+
+            psTrace("psModules.imcombine", 6, "subCalcEqn(): stamp %d: generated spatial order terms.\n", s);
+
+            if (kernels->type == PM_SUBTRACTION_KERNEL_POIS) {
+                //
+                // Iterate over all pixels surrounding this stamp.
+                //
+                for (psS32 y = stamp->y - footprint; y < stamp->y + footprint; y++) {
+                    for (psS32 x = stamp->x - footprint; x < stamp->x + footprint; x++) {
+                        psTrace("psModules.imcombine", 6, "subCalcEqn(): pixel (%d, %d).\n", y, x);
+
+                        // The inverse of the noise, squared.
+                        psF32 invNoise2 = 1.0/reference->data.F32[y][x];
+
+                        //
+                        // Iterate over the first convolution */
+                        //
+                        for (psS32 k1 = 0; k1 < numKernels; k1++) {
+                            psS32 u1 = kernels->u->data.F32[k1];        // Offset in x
+                            psS32 v1 = kernels->v->data.F32[k1];        // Offset in y
+                            psS32 i1 = kernels->xOrder->data.F32[k1];   // Polynomial order in x
+                            psS32 j1 = kernels->yOrder->data.F32[k1];   // Polynomial order in y
+
+                            //
+                            // First convolution.  This will set the value for the stampVector.
+                            //
+                            // XXX: verify the [y-v2][x-u2] subscript.  This generated errors in
+                            // testing, depending on kernel size and footprint.
+                            //
+                            psF32 conv1 = polyValues->data.F64[j1][i1] * reference->data.F32[y - v1][x - u1];
+
+                            //
+                            // Assuming that the first kernel component is 0 order in x and y, and 0 offset
+                            // XXX: I don't understand this:
+                            //
+                            if (k1 != 0) {
+                                conv1 -= reference->data.F32[y][x];
+                            }
+
+                            //
+                            // Iterate over the second convolution
+                            //
+                            for (psS32 k2 = k1; k2 < numKernels; k2++) {
+                                psS32 u2 = (psS32) kernels->u->data.F32[k2];        // Offset in x
+                                psS32 v2 = (psS32) kernels->v->data.F32[k2];        // Offset in y
+                                psS32 i2 = (psS32) kernels->xOrder->data.F32[k2];   // Polynomial order in x
+                                psS32 j2 = (psS32) kernels->yOrder->data.F32[k2];   // Polynomial order in y
+                                //
+                                // XXX: verify the [y-v2][x-u2] subscript.  This generated errors in
+                                // testing, depending on kernel size and footprint.
+                                //
+                                // Second convolution
+                                //
+                                psF32 conv2 = polyValues->data.F64[j2][i2] *
+                                              reference->data.F32[y-v2][x-u2];
+                                //
+                                // Assuming that the first kernel component is 0 order in x and y, and 0 offset
+                                // XXX: I don't understand this:
+                                if (k2 != 0) {
+                                    //
+                                    conv2 -= reference->data.F32[y][x];
+                                }
+
+                                // Add into the matrix element
+                                stampMatrix->data.F64[k1][k2] += conv1 * conv2 * invNoise2;
+
+                            } // Iteration on second convolution
+
+                            // Add into the vector element
+                            stampVector->data.F64[k1] += input->data.F32[y][x] * conv1 * invNoise2;
+
+                            /* Background term */
+                            stampMatrix->data.F64[k1][bgIndex] += conv1 * invNoise2;
+
+                        } // Iteration on first convolution
+
+                        //
+                        // Background only terms.
+                        // XXX: understand this.
+                        //
+                        stampMatrix->data.F64[bgIndex][bgIndex] += invNoise2;
+                        stampVector->data.F64[bgIndex] += input->data.F32[y][x] * invNoise2;
+                    }
+                }
+            } else if (kernels->type == PM_SUBTRACTION_KERNEL_ISIS) {
+                //
+                // Iterate over all pixels surrounding this stamp.
+                //
+                // XXX: Why isn't there a polyValues term here?
+                //
+
+                for (psS32 y = stamp->y - footprint; y < stamp->y + footprint; y++) {
+                    for (psS32 x = stamp->x - footprint; x < stamp->x + footprint; x++) {
+                        psTrace("psModules.imcombine", 6, "subCalcEqn(): pixel (%d, %d).\n", y, x);
+                        psF32 invNoise2 = 1.0/reference->data.F32[y][x]; // The inverse of the noise, squared.
+
+                        for (psS32 k1 = 0; k1 < numKernels; k1++) {
+                            psF32 conv1 = IsisKernelConvolve(reference, kernels, k1, x, y);
+
+                            for (psS32 k2 = k1; k2 < numKernels; k2++) {
+                                //printf("(k1, k2) is (%d, %d)\n", k1, k2);
+                                psF32 conv2 = IsisKernelConvolve(reference, kernels, k2, x, y);
+                                stampMatrix->data.F64[k1][k2]+= conv1 * conv2 * invNoise2;
+                            }
+                            stampVector->data.F64[k1]+= input->data.F32[y][x] * conv1 * invNoise2;
+                            stampMatrix->data.F64[k1][bgIndex] += conv1 * invNoise2;
+                        }
+                        stampMatrix->data.F64[bgIndex][bgIndex] += invNoise2;
+                        stampVector->data.F64[bgIndex] += input->data.F32[y][x] * invNoise2;
+                    }
+                }
+            } else {
+                psLogMsg(__func__, PS_LOG_WARN, "WARNING: unknown kernel->type.\n");
+                return(false);
+            }
+            psFree(polyValues);
+
+            // XXX: Generate psTrace()
+            if (0) {
+                for (psS32 s = 0; s < stamps->n; s++) {
+                    pmStamp *stamp = (pmStamp *) stamps->data[s];
+                    if (stamp->status == PM_STAMP_RECALC) {
+                        psVector *stampVector = stamp->vector;
+                        printf("STAMP: stamp %d vector:\n", s);
+                        PS_VECTOR_PRINT_F64(stampVector);
+                    }
+                }
+            }
+
+            //
+            // Fill in other side of symmetric matrix
+            //
+            // XXX: understand this.
+            // XXX: Why aren't they using numSolveParams instead of numKernels?
+            // XXX: is this POIS specific?
+            //
+            for (psS32 k1 = 0; k1 < numKernels; k1++) {
+                for (psS32 k2 = 0; k2 < k1; k2++) {
+                    stampMatrix->data.F64[k1][k2] = stampMatrix->data.F64[k2][k1];
+                }
+                stampMatrix->data.F64[bgIndex][k1] = stampMatrix->data.F64[k1][bgIndex];
+            }
+
+            //
+            // XXX: Why aren't they using numSolveParams instead of numKernels?
+            // XXX: is this POIS specific?
+            //
+            #define XXX_CONFIG_PENALTY 1.0
+            for (psS32 k = 0; k < numKernels; k++)
+            {
+                psS32 u = kernels->u->data.F32[k];  // Offset in x
+                psS32 v = kernels->v->data.F32[k];  // Offset in y
+                stampMatrix->data.F64[k][k] += XXX_CONFIG_PENALTY * (psF32)(u*u + v*v);
+            }
+            stamp->status = PM_STAMP_USED;
+        } else {
+            // Stamp is ignored since it's not PM_STAMP_RECALC
+        }
+    }
+    psTrace("psModules.imcombine", 3,
+            "Exiting pmSubtractionCalculateEquation()\n");
+    return(true);
+}
+
+
+
+
+/*******************************************************************************
+ ******************************************************************************/
+psVector *pmSubtractionSolveEquation(psVector *solution, ///< Solution vector, or NULL
+                                     const psArray *stamps      ///< Array of stamps
+                                    )
+{
+    psTrace("psModules.imcombine", 3,
+            "Calling pmSubtractionSolveEquation()\n");
+    PS_ASSERT_PTR_NON_NULL(stamps, NULL);
+    psS32 size = -1;
+    psS32 s = 0;
+
+    //
+    // Determine the size of the stamp vectors and matrix.
+    // We iterate until we find the first acceptable stamp.
+    //
+    while ((size == -1) && (s < stamps->n)) {
+        pmStamp *stamp = (pmStamp *) stamps->data[s];
+        PS_ASSERT_PTR_NON_NULL(stamp, NULL);
+        if (stamp->status == PM_STAMP_USED) {
+            size = ((pmStamp *) stamps->data[s])->vector->n;
+            PS_ASSERT_INT_POSITIVE(size, NULL);
+        }
+        s++;
+    }
+    if (size == -1) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: no acceptable stamps.  Returning NULL\n");
+        return(NULL);
+    }
+
+    if (solution != NULL) {
+        PS_ASSERT_VECTOR_TYPE(solution, PS_TYPE_F64, NULL);
+        PS_ASSERT_VECTOR_SIZE(solution, (long)size, NULL);
+    } else {
+        solution = psVectorAlloc(size, PS_TYPE_F64);
+        solution->n = solution->nalloc;
+    }
+
+    //
+    // Create the solution matrix and vector.
+    //
+    // XXX: Test these functions with size=-1.  This caused seg faults during test.
+    //      This should be done in the psImage.c and psVector.c test files.  It
+    //      should never occur here.
+    //
+    psImage *sumMatrix = psImageAlloc(size, size, PS_TYPE_F64);
+    psVector *sumVector = psVectorAlloc(size, PS_TYPE_F64);
+    sumVector->n = sumVector->nalloc;
+    psVectorInit(sumVector, 0.0);
+    psImageInit(sumMatrix, 0.0);
+
+    //
+    // Verify that all stamps have similar sizes.
+    // Compute the sum matrix and vector.
+    //
+    for (psS32 s = 0; s < stamps->n; s++) {
+        pmStamp *stamp = (pmStamp *) stamps->data[s];
+
+        if (stamp->status == PM_STAMP_USED) {
+            PS_ASSERT_INT_EQUAL(((pmStamp *) stamps->data[s])->vector->n, size, NULL);
+
+            psImage *stampMatrix = stamp->matrix;
+            psVector *stampVector = stamp->vector;
+            PS_ASSERT_VECTOR_TYPE(stampVector, PS_TYPE_F64, NULL);
+            PS_ASSERT_VECTOR_SIZE(stampVector, (long)size, NULL);
+            PS_ASSERT_IMAGE_TYPE(stampMatrix, PS_TYPE_F64, NULL);
+            PS_ASSERT_IMAGE_SIZE(stampMatrix, size, size, NULL);
+
+            (void)psBinaryOp(sumMatrix, sumMatrix, "+", stampMatrix);
+            (void)psBinaryOp(sumVector, sumVector, "+", stampVector);
+        }
+    }
+    psVector *permutation = NULL;
+    // XXX: Check output from these routines.
+
+    // XXX: psTrace()
+    if (0) {
+        PS_IMAGE_PRINT_F64(sumMatrix);
+    }
+
+    psImage *luMatrix = psMatrixLUD(NULL, &permutation, sumMatrix);
+    if (luMatrix == NULL) {
+        psError(PS_ERR_UNKNOWN, true, "Failed to LU-Decompose the matrix.\n");
+        psFree(sumMatrix);
+        psFree(sumVector);
+        psFree(luMatrix);
+        psFree(permutation);
+        return(NULL);
+    }
+    // XXX: psTrace()
+    if (0) {
+        PS_IMAGE_PRINT_F64(luMatrix);
+    }
+
+    solution = psMatrixLUSolve(solution, luMatrix, sumVector, permutation);
+    // XXX: psTrace()
+    // XXX: should we be checking for NAN's in the solution vector?
+    if (0) {
+        PS_VECTOR_PRINT_F64(solution);
+    }
+    if (solution == NULL) {
+        psError(PS_ERR_UNKNOWN, true, "Failed to solve the matrix.\n");
+        psFree(sumMatrix);
+        psFree(sumVector);
+        psFree(luMatrix);
+        psFree(permutation);
+        return(NULL);
+    }
+
+    psFree(sumMatrix);
+    psFree(sumVector);
+    psFree(luMatrix);
+    psFree(permutation);
+
+    psTrace("psModules.imcombine", 3,
+            "Exiting pmSubtractionSolveEquation()\n");
+    return(solution);
+}
+
+
+/*******************************************************************************
+ ******************************************************************************/
+static psVector *CalculateDeviations(psVector *deviations,
+                                     psArray *stamps,
+                                     psS32 footprint,
+                                     const psImage *refImage,
+                                     const psImage *inImage,
+                                     const psImage *mask,
+                                     psU32 badStampMaskVal,
+                                     const psSubtractionKernels *kernels,
+                                     const psVector *solution)
+{
+    psTrace("psModules.imcombine", 4,
+            "Calling CalculateDeviations()\n");
+    PS_ASSERT_PTR_NON_NULL(stamps, NULL);
+    if (deviations != NULL) {
+        PS_ASSERT_VECTOR_TYPE(deviations, PS_TYPE_F32, NULL);
+        PS_ASSERT_VECTORS_SIZE_EQUAL(deviations, stamps, NULL);
+    } else {
+        deviations = psVectorAlloc(stamps->n, PS_TYPE_F32);
+        deviations->n = deviations->nalloc;
+        // XXX: Probably not necessary.
+        psVectorInit(deviations, 0.0);
+    }
+    PS_ASSERT_IMAGE_NON_NULL(refImage, NULL);
+    PS_ASSERT_IMAGE_NON_EMPTY(refImage, NULL);
+    PS_ASSERT_IMAGE_TYPE(refImage, PS_TYPE_F32, NULL);
+    PS_ASSERT_IMAGE_NON_NULL(inImage, NULL);
+    PS_ASSERT_IMAGE_NON_EMPTY(inImage, NULL);
+    PS_ASSERT_IMAGE_TYPE(inImage, PS_TYPE_F32, NULL);
+    PS_ASSERT_IMAGES_SIZE_EQUAL(refImage, inImage, NULL);
+    PS_ASSERT_IMAGE_NON_NULL(mask, NULL);
+    PS_ASSERT_IMAGE_NON_EMPTY(mask, NULL);
+    PS_ASSERT_IMAGE_TYPE(mask, PS_TYPE_U8, NULL);
+    PS_ASSERT_IMAGES_SIZE_EQUAL(refImage, mask, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(solution, NULL);
+    PS_ASSERT_VECTOR_TYPE(solution, PS_TYPE_F64, NULL);
+    PS_ASSERT_PTR_NON_NULL(kernels, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->v, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->xOrder, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->yOrder, NULL);
+    if (kernels->preCalc != NULL) {
+        PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->preCalc, NULL);
+    } else {
+        if (kernels->type != PM_SUBTRACTION_KERNEL_POIS) {
+            psError(PS_ERR_BAD_PARAMETER_NULL, true,
+                    "Unallowable operation: kernels->preCalc == NULL and kernels->type != PM_SUBTRACTION_KERNEL_POIS.\n");
+            return(NULL);
+        }
+    }
+    psS32 nBF = kernels->u->n;
+    PS_ASSERT_VECTOR_SIZE(solution, (long)nBF+1, NULL);
+
+    psS32 kernelSize = kernels->size;
+    int xSize = footprint + kernelSize;
+    int ySize = footprint + kernelSize;
+    psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN); // Statistics
+    psImage *subStamp = psImageAlloc(2 * xSize, 2 * ySize, PS_TYPE_F32); // Subtraction of stamp
+    for (psS32 s = 0; s < stamps->n; s++) {
+        pmStamp *stamp = stamps->data[s]; // The coordinates of the stamp of interest
+        psS32 x = stamp->x;               // Stamp x coord
+        psS32 y = stamp->y;               // Stamp y coord
+        if (stamp->status == PM_STAMP_USED) {
+
+            psRegion myReg = psRegionSet(x - xSize, x + xSize, y - ySize, y + ySize);
+            psImage *refStamp = psImageSubset((psImage *) refImage, myReg);
+            psImage *inStamp = psImageSubset((psImage *) inImage, myReg);
+            psImage *maskStamp = psImageSubset((psImage *) mask, myReg);
+            psImage *convRefStamp = ConvolveImage(refStamp, maskStamp, badStampMaskVal, solution, kernels);
+
+            // Calculate chi^2
+            (void)psBinaryOp(subStamp, inStamp, "-", convRefStamp);
+            (void)psBinaryOp(subStamp, subStamp, "/", inStamp);
+            (void)psBinaryOp(subStamp, subStamp, "*", subStamp);
+            myReg = psRegionSet(kernelSize, kernelSize + 2 * footprint,
+                                kernelSize, kernelSize + 2 * footprint);
+            psImage *subStampTrim = psImageSubset((psImage *) subStamp, myReg);
+            psImage *maskStampTrim = psImageSubset((psImage *) maskStamp, myReg);
+            psImageStats(stats, subStampTrim, maskStampTrim, badStampMaskVal);
+
+            deviations->data.F32[s] = stats->sampleMean * (psF32)footprint * (psF32)footprint * 4.0;
+            // XXX: Allocate and free these elsewhere.
+            psFree(refStamp);
+            psFree(inStamp);
+            psFree(maskStamp);
+            psFree(convRefStamp);
+            psFree(subStampTrim);
+            psFree(maskStampTrim);
+        }
+    }
+
+    psFree(stats);
+    psFree(subStamp);
+
+    psTrace("psModules.imcombine", 4,
+            "Exiting CalculateDeviations()\n");
+    return deviations;
+}
+
+/*******************************************************************************
+ ******************************************************************************/
+bool pmSubtractionRejectStamps(psArray *stamps,  ///< Array of stamps to check for rejection
+                               psImage *mask,  ///< Mask image
+                               psU32 badStampMaskVal, ///< Value to use in mask for bad stamp
+                               psS32 footprint,  ///< Region to mask if stamp is bad
+                               psF32 sigmaRej,  ///< Number of RMS deviations above zero at which to reject
+                               const psImage *refImage, ///< Reference image
+                               const psImage *inImage, ///< Input image
+                               const psVector *solution, ///< Solution vector
+                               const psSubtractionKernels *kernels ///< Array of kernel parameters
+                              )
+{
+    psTrace("psModules.imcombine", 3,
+            "Calling pmSubtractionRejectStamps()\n");
+    PS_ASSERT_PTR_NON_NULL(stamps, false);
+    PS_ASSERT_IMAGE_NON_NULL(refImage, false);
+    PS_ASSERT_IMAGE_NON_EMPTY(refImage, false);
+    PS_ASSERT_IMAGE_TYPE(refImage, PS_TYPE_F32, false);
+    PS_ASSERT_IMAGE_NON_NULL(inImage, false);
+    PS_ASSERT_IMAGE_NON_EMPTY(inImage, false);
+    PS_ASSERT_IMAGE_TYPE(inImage, PS_TYPE_F32, false);
+    PS_ASSERT_IMAGES_SIZE_EQUAL(refImage, inImage, false);
+    PS_ASSERT_IMAGE_NON_NULL(mask, false);
+    PS_ASSERT_IMAGE_NON_EMPTY(mask, false);
+    PS_ASSERT_IMAGE_TYPE(mask, PS_TYPE_U8, false);
+    PS_ASSERT_IMAGES_SIZE_EQUAL(refImage, mask, false);
+    PS_ASSERT_VECTOR_NON_NULL(solution, false);
+    PS_ASSERT_VECTOR_TYPE(solution, PS_TYPE_F64, false);
+    PS_ASSERT_PTR_NON_NULL(kernels, false);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->v, false);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->xOrder, false);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->yOrder, false);
+    if (kernels->preCalc != NULL) {
+        PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->preCalc, false);
+    } else {
+        if (kernels->type != PM_SUBTRACTION_KERNEL_POIS) {
+            psError(PS_ERR_BAD_PARAMETER_NULL, true,
+                    "Unallowable operation: kernels->preCalc == NULL and kernels->type != PM_SUBTRACTION_KERNEL_POIS.\n");
+            return(false);
+        }
+    }
+
+    psS32 nBF = kernels->u->n;
+    PS_ASSERT_VECTOR_SIZE(solution, (long)nBF+1, false);
+
+    psVector *deviations = CalculateDeviations(NULL,
+                           stamps,
+                           footprint,
+                           refImage,
+                           inImage,
+                           mask,
+                           badStampMaskVal,
+                           kernels,
+                           solution);
+    //
+    // Calculate the deviation from zero.
+    //
+    psF64 meanDev = 0.0;
+    psS32 numDev = 0;
+    for (psS32 i = 0; i < deviations->n; i++) {
+        pmStamp *stamp = stamps->data[i];
+        if (stamp->status == PM_STAMP_USED) {
+            meanDev += PS_SQR(deviations->data.F32[i]);
+            numDev++;
+        }
+    }
+    psF32 rmsDev = sqrtf(meanDev / (psF64)(numDev - 1));
+    psF32 limit = rmsDev * sigmaRej;
+
+    for (psS32 s = 0; s < stamps->n; s++) {
+        pmStamp *stamp = (pmStamp *) stamps->data[s];
+        if (stamp->status == PM_STAMP_USED && fabsf(deviations->data.F32[s]) > limit) {
+            // Mask out the stamp in the image so you don't find it again
+            for (psS32 y = stamp->y - footprint; y < stamp->y + footprint; y++) {
+                for (psS32 x = stamp->x - footprint; x < stamp->x + footprint; x++) {
+                    mask->data.U8[y][x] |= badStampMaskVal;
+                }
+            }
+
+            // Set stamp for replacement
+            stamp->x = 0;
+            stamp->y = 0;
+            stamp->status = PM_STAMP_REJECTED;
+        }
+    }
+
+    psFree(deviations);
+    psTrace("psModules.imcombine", 3,
+            "Exiting pmSubtractionRejectStamps()\n");
+    return(true);
+}
+
+/*******************************************************************************
+ ******************************************************************************/
+psImage *pmSubtractionKernelImage(psImage *out,
+                                  const psVector *solution,
+                                  const psSubtractionKernels *kernels,
+                                  psF32 x,
+                                  psF32 y
+                                 )
+{
+    psTrace("psModules.imcombine", 3,
+            "Calling pmSubtractionKernelImage()\n");
+    PS_ASSERT_VECTOR_NON_NULL(solution, NULL);
+    PS_ASSERT_PTR_NON_NULL(kernels, NULL);
+    PS_ASSERT_FLOAT_WITHIN_RANGE(x, -1.0, 1.0, NULL);
+    PS_ASSERT_FLOAT_WITHIN_RANGE(y, -1.0, 1.0, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->v, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->xOrder, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->yOrder, NULL);
+    if (kernels->preCalc != NULL) {
+        PS_ASSERT_VECTORS_SIZE_EQUAL(kernels->u, kernels->preCalc, NULL);
+    } else {
+        if (kernels->type != PM_SUBTRACTION_KERNEL_POIS) {
+            psError(PS_ERR_BAD_PARAMETER_NULL, true,
+                    "Unallowable operation: kernels->preCalc == NULL and kernels->type != PM_SUBTRACTION_KERNEL_POIS.\n");
+            return(NULL);
+        }
+    }
+    PS_ASSERT_INT_EQUAL(1+kernels->u->n, solution->n, NULL);
+
+    psS32 nBF = kernels->u->n;
+    psS32 spatialOrder = kernels->spatialOrder;
+    psS32 kernelSize = kernels->size;
+
+    if (out != NULL) {
+        if ((out->numCols < (1+2*kernelSize)) || (out->numRows < (1+2*kernelSize))) {
+            psLogMsg(__func__, PS_LOG_WARN, "WARNING: out image is not large enough.\n");
+            return(out);
+        }
+    } else {
+        out = psImageAlloc(1+2*kernelSize, 1+2*kernelSize, PS_TYPE_F32);
+    }
+    psImageInit(out, 0.0);
+
+    //
+    // Generate the spatial-order polynomial.  The [i][j]-th element of
+    // the psImage polyValues will hold (x^i * y^j) for the stamp.
+    //
+    psImage *polyValues = GenSpatialOrder(spatialOrder, x, y);
+
+    // XXX: switch (i, j) so they correspond to (x, y).
+    if (kernels->type == PM_SUBTRACTION_KERNEL_ISIS) {
+        for (psS32 k = 0 ; k < nBF ; k++) {
+            psS32 xOrder = (psS32) kernels->xOrder->data.F32[k];
+            psS32 yOrder = (psS32) kernels->yOrder->data.F32[k];
+            psF64 polyVal = polyValues->data.F64[yOrder][xOrder];
+
+            // XXX: Verify that this is correct.
+            for (psS32 i = -kernelSize ; i <= kernelSize ; i++) {
+                for (psS32 j = -kernelSize ; j <= kernelSize ; j++) {
+                    psImage *preCalc = (psImage *) kernels->preCalc->data[k];
+                    out->data.F32[i+kernelSize][j+kernelSize]+=
+                        solution->data.F64[k] *
+                        preCalc->data.F32[i+kernelSize][j+kernelSize] *
+                        polyVal;
+                }
+            }
+        }
+    } else if (kernels->type == PM_SUBTRACTION_KERNEL_POIS) {
+        for (psS32 k = 0 ; k < nBF ; k++) {
+            // XXX: Why don't we have compilation warnings on type here (if
+            // we remove the (psS32) cast)?
+            psS32 u = (psS32) kernels->u->data.F32[k];
+            psS32 v = (psS32) kernels->v->data.F32[k];
+            psS32 xOrder = (psS32) kernels->xOrder->data.F32[k];
+            psS32 yOrder = (psS32) kernels->yOrder->data.F32[k];
+            // XXX: Verify that this is correct.
+
+            out->data.F32[kernelSize - v][kernelSize - u]+=
+                solution->data.F64[k] * polyValues->data.F64[yOrder][xOrder];
+        }
+    }
+    psFree(polyValues);
+
+    psTrace("psModules.imcombine", 3,
+            "Exiting pmSubtractionKernelImage()\n");
+    return(out);
+}
Index: /tags/RHL_v1_1/psModules/src/imcombine/pmImageSubtract.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/imcombine/pmImageSubtract.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/imcombine/pmImageSubtract.h	(revision 9533)
@@ -0,0 +1,127 @@
+/** @file  ImageSubtract.h
+ *
+ *  This file will contain code which creates a set of kernel basis
+ *  functions, solves for their solution, and applies them to an image.
+ *
+ *  @author Paul Price, IfA (original prototype)
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-05-01 01:56:48 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#if !defined(PM_IMAGE_COMBINE_H)
+#define PM_IMAGE_COMBINE_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include<stdio.h>
+#include<math.h>
+#include "pslib.h"
+#include "psConstants.h"
+
+typedef enum {
+    PM_SUBTRACTION_KERNEL_POIS,         ///< POIS kernel --- delta functions
+    PM_SUBTRACTION_KERNEL_ISIS          ///< ISIS kernel --- gaussians modified by polynomials
+} pmSubtractionKernelsType;
+
+typedef struct
+{
+    pmSubtractionKernelsType type;      ///< Type ofKernels --- allowing the use of multiple kernels
+    psVector *u, *v;                    ///< Offset (for POIS) or polynomial order (for ISIS)
+    psVector *sigma;                    ///< Width of Gaussian (for ISIS)
+    psVector *xOrder, *yOrder;          ///< Spatial Polynomial order (for all)
+    int subIndex;                       ///< Index of kernel to be subtracted to maintain flux conservation
+    psArray *preCalc;                   ///< Array of images containing pre-calculated kernel (to
+    ///< accelerate ISIS; don't use for POIS)
+    psS32 size;                         ///< The halfsize of the kernel
+    psS32 spatialOrder;                 ///< The spatial order of the kernels
+}
+psSubtractionKernels;
+
+psSubtractionKernels *pmSubtractionKernelsAllocPOIS(
+    int size,
+    int SpatialOrder
+);
+
+psSubtractionKernels *pmSubtractionKernelsAllocISIS(
+    const psVector *sigmas,
+    const psVector *orders,
+    int size,
+    int SpatialOrder
+);
+
+typedef enum {
+    PM_STAMP_INIT,                      ///< Initial state
+    PM_STAMP_USED,                      ///< Use this stamp
+    PM_STAMP_REJECTED,                  ///< This stamp has been rejected
+    PM_STAMP_RECALC,                    ///< Having been reset, this stamp is to be recalculated
+    PM_STAMP_NONE                       ///< No stamp in this region
+} pmStampStatus;
+
+typedef struct
+{
+    int x, y;                           ///< Position
+    int p_xSize;
+    int p_ySize;
+    int p_xMin;
+    int p_xMax;
+    int p_yMin;
+    int p_yMax;
+    psImage *matrix;                    ///< Associated matrix
+    psVector *vector;                   ///< Assoicated vector
+    pmStampStatus status;               ///< Status ofstamp
+}
+pmStamp;
+
+psArray *pmSubtractionFindStamps(
+    psArray *stamps,                    ///< Output stamps, or NULL
+    const psImage *image,               ///< Image for which to find stamps
+    const psImage *mask,                ///< Mask
+    psU32 maskVal,                      ///< Value for mask
+    psF32 threshold,                    ///< Threshold for stamps in the image
+    psS32 xNum,                         ///< Number of stamps in x
+    psS32 yNum,                         ///< Number of stamps in y
+    psS32 border                        ///< Border around image to ignore (should be size of kernel)
+);
+
+bool pmSubtractionCalculateEquation(
+    psArray *stamps,                    ///< The stamps for which to calculate the equation,
+    const psImage *reference,           ///< Reference image
+    const psImage *input,               ///< Input image
+    const psSubtractionKernels *kernels,///< The kernel basis functions
+    psS32 footprint                     ///< Half-size of region over which to calculate equation
+);
+
+
+psVector *pmSubtractionSolveEquation(
+    psVector *solution,                 ///< Solution vector, or NULL
+    const psArray *stamps               ///< Array of stamps
+);
+
+bool pmSubtractionRejectStamps(
+    psArray *stamps,                    ///< Array of stamps to check for rejection
+    psImage *mask,                      ///< Mask image
+    psU32 badStampMaskVal,              ///< Value to use in mask for bad stamp
+    psS32 footprint,                    ///< Region to mask if stamp is bad
+    psF32 sigmaRej,                     ///< Number of RMS deviations above zero at which to reject
+    const psImage *refImage,            ///< Reference image
+    const psImage *inImage,             ///< Input image
+    const psVector *solution,           ///< Solution vector
+    const psSubtractionKernels *kernels ///< Array of kernel parameters
+);
+
+psImage *pmSubtractionKernelImage(
+    psImage *out,
+    const psVector *solution,
+    const psSubtractionKernels *kernels,
+    psF32 x,
+    psF32 y
+);
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/imcombine/pmReadoutCombine.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/imcombine/pmReadoutCombine.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/imcombine/pmReadoutCombine.c	(revision 9533)
@@ -0,0 +1,414 @@
+/** @file  pmReadoutCombine.c
+ *
+ *  This file will contain a module which will combine multiple readout images.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.26 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-20 02:36:36 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <pslib.h>
+
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmConceptsAverage.h"
+
+#include "pmReadoutCombine.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static (private) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Return the statistic of interest
+static inline double getStat(const psStats *stats, // Statistics structure
+                             psStatsOptions option // Statistics option
+                            )
+{
+    assert(stats);
+
+    switch (option) {
+    case PS_STAT_SAMPLE_MEAN:
+        return stats->sampleMean;
+    case PS_STAT_SAMPLE_MEDIAN:
+        return stats->sampleMedian;
+    case PS_STAT_SAMPLE_STDEV:
+        return stats->sampleStdev;
+    case PS_STAT_ROBUST_MEDIAN:
+        return stats->robustMedian;
+    case PS_STAT_ROBUST_STDEV:
+        return stats->robustStdev;
+    case PS_STAT_FITTED_MEAN:
+        return stats->fittedMean;
+    case PS_STAT_FITTED_STDEV:
+        return stats->fittedStdev;
+    case PS_STAT_CLIPPED_MEAN:
+        return stats->clippedMean;
+    case PS_STAT_CLIPPED_STDEV:
+        return stats->clippedStdev;
+    case PS_STAT_MAX:
+        return stats->max;
+    case PS_STAT_MIN:
+        return stats->min;
+    default:
+        psAbort(__func__, "Bad option: %x\n", option);
+    }
+    return NAN;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Allocator for pmCombineParams
+pmCombineParams *pmCombineParamsAlloc(psStatsOptions combine)
+{
+    pmCombineParams *params = psAlloc(sizeof(pmCombineParams));
+
+    params->combine = combine;
+    params->maskVal = 0;
+    params->nKeep = 0;
+    params->fracHigh = 0.0;
+    params->fracHigh = 0.0;
+    params->iter = 1;
+    params->rej = 3.0;
+
+    return params;
+}
+
+
+/******************************************************************************
+XXX: Maybe add support for S16 and S32 types.  Currently, only F32 supported.
+ *****************************************************************************/
+bool pmReadoutCombine(pmReadout *output,
+                      psArray *inputs,
+                      const psVector *zero,
+                      const psVector *scale,
+                      pmCombineParams *params
+                     )
+{
+    // Check inputs
+    PS_ASSERT_PTR_NON_NULL(output, false);
+    PS_ASSERT_PTR_NON_NULL(inputs, false);
+    PS_ASSERT_PTR_NON_NULL(params, false);
+    if (zero) {
+        PS_ASSERT_VECTOR_TYPE(zero, PS_TYPE_F32, false);
+        PS_ASSERT_VECTOR_SIZE(zero, inputs->n, false);
+    }
+    if (scale) {
+        PS_ASSERT_VECTOR_TYPE(scale, PS_TYPE_F32, false);
+        PS_ASSERT_VECTOR_SIZE(scale, inputs->n, false);
+    }
+    PS_ASSERT_FLOAT_WITHIN_RANGE(params->fracLow, 0.0, 1.0, false);
+    PS_ASSERT_FLOAT_WITHIN_RANGE(params->fracHigh, 0.0, 1.0, false);
+    if (params->combine != PS_STAT_SAMPLE_MEAN && params->combine != PS_STAT_SAMPLE_MEDIAN &&
+            params->combine != PS_STAT_ROBUST_MEDIAN && params->combine != PS_STAT_FITTED_MEAN &&
+            params->combine != PS_STAT_CLIPPED_MEAN) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, true, "Combination method is not SAMPLE_MEAN, SAMPLE_MEDIAN, "
+                "ROBUST_MEDIAN, FITTED_MEAN or CLIPPED_MEAN.\n");
+        return false;
+    }
+
+    psStats *stats = psStatsAlloc(params->combine); // The statistics to use in the combination
+    if (params->combine == PS_STAT_CLIPPED_MEAN) {
+        stats->clipSigma = params->rej;
+        stats->clipIter = params->iter;
+    }
+
+    // Step through each readout in the input image list to determine how big of an output image is needed to
+    // combine these input images.
+    int maxInputCols = 0;               // The largest input column value
+    int maxInputRows = 0;               // The largest input row value
+    int minInputCols = INT_MAX;         // The smallest input column value
+    int minInputRows = INT_MAX;         // The smallest input row value
+    int xSize = 0, ySize = 0;           // The size of the output image
+
+    bool haveWeights = false;           // Do we have weight images?
+    bool valid = false;                 // Do we have a single valid input?
+    for (long i = 0; i < inputs->n; i++) {
+        pmReadout *readout = inputs->data[i]; // Readout of interest
+        pmCell *cell = readout->parent; // The parent cell
+
+        if (!readout || !readout->image) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Input readout %ld is NULL or has NULL image.\n", i);
+            return false;
+        }
+
+        bool mdok = true;       // Status of MD lookup
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, cell->concepts, "CELL.TRIMSEC"); // Trim section
+        if (!mdok || !trimsec || psRegionIsNaN(*trimsec)) {
+            psLogMsg(__func__, PS_LOG_WARN, "CELL.TRIMSEC is not set for readout %ld --- ignored.\n", i);
+        } else {
+            xSize = PS_MAX(xSize, trimsec->x1 - trimsec->x0);
+            ySize = PS_MAX(ySize, trimsec->y1 - trimsec->y0);
+        }
+
+        if (readout->weight) {
+            if (valid && !haveWeights) {
+                psLogMsg(__func__, PS_LOG_WARN, "Readout %ld has a weight map, but others don't --- "
+                         "weights ignored.\n", i);
+            } else {
+                haveWeights = true;
+            }
+        } else if (haveWeights) {
+            psLogMsg(__func__, PS_LOG_WARN, "Readout %ld doesn't have a weight map, but others do --- "
+                     "weights ignored.\n", i);
+            haveWeights = false;
+        }
+        valid = true;
+
+        // Range of pixels on output image
+        minInputRows = PS_MIN(minInputRows, readout->row0);
+        maxInputRows = PS_MAX(maxInputRows, readout->row0 + readout->image->numRows);
+        minInputCols = PS_MIN(minInputCols, readout->col0);
+        maxInputCols = PS_MAX(maxInputCols, readout->col0 + readout->image->numCols);
+        psTrace("psModules.imcombine", 7, "Readout %ld: offset %d,%d; size %dx%d\n", i,
+                readout->col0, readout->row0, readout->image->numCols, readout->image->numRows);
+    }
+    if (!valid) {
+        psError(PS_ERR_IO, true, "No valid inputs.\n");
+        return NULL;
+    }
+
+    // Update the origin
+    if (output->image) {
+        *(psS32 *) &(output->col0) = PS_MIN(minInputCols, output->col0);
+        *(psS32 *) &(output->row0) = PS_MIN(minInputRows, output->row0);
+    } else {
+        *(psS32 *) &(output->col0) = minInputCols;
+        *(psS32 *) &(output->row0) = minInputRows;
+    }
+    psTrace("psModules.imcombine", 7, "Output minimum: %d,%d\n", output->col0, output->row0);
+
+    // Allocate the output products
+
+    if (!output->image) {
+        output->image = psImageAlloc(xSize, ySize, PS_TYPE_F32);
+    }
+    if (output->image->numCols < xSize || output->image->numRows < ySize) {
+        // Generate the new output image by extending the current one, or making a whole new one
+        psImage *newImage = psImageAlloc(xSize, ySize, PS_TYPE_F32);
+        psImageInit(newImage, 0.0);
+        psImageOverlaySection(newImage, output->image, output->col0, output->row0, "=");
+        psFree(output->image);
+        output->image = newImage;
+    }
+
+    if (!output->mask) {
+        output->mask = psImageAlloc(xSize, ySize, PS_TYPE_U8);
+    }
+    if (output->mask->numCols < xSize || output->mask->numRows < ySize) {
+        psImage *newMask = psImageAlloc(xSize, ySize, PS_TYPE_U8);
+        psImageInit(newMask, 0);
+        psImageOverlaySection(newMask, output->mask, output->col0, output->row0, "=");
+        psFree(output->mask);
+        output->mask = newMask;
+    }
+
+    psStatsOptions combineStdev = 0; // Statistics option for weights
+    if (haveWeights) {
+
+        if (!output->weight) {
+            output->weight = psImageAlloc(xSize, ySize, PS_TYPE_F32);
+        }
+        if (output->weight->numCols < xSize || output->weight->numRows < ySize) {
+            psImage *newWeight = psImageAlloc(xSize, ySize, PS_TYPE_F32);
+            psImageInit(newWeight, 0.0);
+            psImageOverlaySection(newWeight, output->weight, output->col0, output->row0, "=");
+            psFree(output->weight);
+            output->weight = newWeight;
+        }
+
+        // Get the correct statistics option for weights
+        switch (params->combine) {
+        case PS_STAT_SAMPLE_MEAN:
+        case PS_STAT_SAMPLE_MEDIAN:
+            combineStdev = PS_STAT_SAMPLE_STDEV;
+            break;
+        case PS_STAT_ROBUST_MEDIAN:
+            combineStdev = PS_STAT_ROBUST_STDEV;
+            break;
+        case PS_STAT_FITTED_MEAN:
+            combineStdev = PS_STAT_FITTED_STDEV;
+            break;
+        case PS_STAT_CLIPPED_MEAN:
+            combineStdev = PS_STAT_CLIPPED_STDEV;
+            break;
+        default:
+            psAbort(__func__, "Should never get here --- checked params->combine before.\n");
+        }
+        stats->options |= combineStdev;
+    }
+
+    // We loop through each pixel in the output image.  We loop through each input readout.  We determine if
+    // that output pixel is contained in the image from that readout.  If so, we save it in psVector pixels.
+    // If not, we set a mask for that element in pixels.  Then, we mask off pixels not between fracLow and
+    // fracHigh.  Then we call the vector stats routine on those pixels/mask.  Then we set the output pixel
+    // value to the result of the stats call.
+
+    psVector *pixels = psVectorAlloc(inputs->n, PS_TYPE_F32); // Stack of pixels
+    pixels->n = inputs->n;
+    psF32 *pixelsData = pixels->data.F32; // Dereference pixels
+
+    psVector *mask   = psVectorAlloc(inputs->n, PS_TYPE_U8); // Mask for stack
+    mask->n = inputs->n;
+    psU8 *maskData = mask->data.U8;     // Dereference mask
+
+    psVector *weights = NULL;           // Stack of weights
+    psF32 *weightsData = NULL;          // Dereference weights
+    if (haveWeights) {
+        weights = psVectorAlloc(inputs->n, PS_TYPE_F32); // Stack of weights
+        weights->n = inputs->n;
+        weightsData = weights->data.F32;
+    }
+    psVector *index = NULL;             // The indices to sort the pixels
+
+    float keepFrac = 1.0 - params->fracLow - params->fracHigh; // Fraction of pixels to keep
+    psMaskType maskVal = params->maskVal; // The mask value
+
+    #ifndef PS_NO_TRACE
+
+    psTrace("psModules.imcombine", 3, "Iterating output: %d --> %d, %d --> %d\n",
+            minInputCols - output->col0, maxInputCols - output->col0,
+            minInputRows - output->row0, maxInputRows - output->row0);
+    if (psTraceGetLevel(__func__) >= 3) {
+        for (int r = 0; r < inputs->n; r++) {
+            pmReadout *readout = inputs->data[r]; // Input readout
+            psTrace("psModules.imcombine", 3, "Iterating input %d: %d --> %d, %d --> %d\n", r,
+                    minInputCols - readout->col0, maxInputCols - readout->col0,
+                    minInputRows - readout->row0, maxInputRows - readout->row0);
+        }
+    }
+    #endif
+
+    // Dereference output products
+    psF32 **outputImage  = output->image->data.F32; // Output image
+    psU8  **outputMask   = output->mask->data.U8; // Output mask
+    psF32 **outputWeight = NULL; // Output weight map
+    if (output->weight) {
+        outputWeight = output->weight->data.F32;
+    }
+
+    psVector *invScale = NULL;          // Inverse scale; pre-calculated for efficiency
+    if (scale) {
+        invScale = (psVector*)psBinaryOp(NULL, psScalarAlloc(1.0, PS_TYPE_F32), "/", (const psPtr)scale);
+    }
+
+    for (int i = minInputRows; i < maxInputRows; i++) {
+        int yOut = i - output->row0; // y position on output readout
+        #if 0
+
+        if (psTraceGetLevel(__func__) > 9) {
+            printf("Processing row %d\r", i);
+            fflush(stdout);
+        }
+        #endif
+        for (int j = minInputCols; j < maxInputCols; j++) {
+            int xOut = j - output->col0; // x position on output readout
+
+            int numValid = 0;           // Number of valid pixels in the stack
+            memset(maskData, 0, mask->n * sizeof(psU8)); // Reset the mask
+            for (int r = 0; r < inputs->n; r++) {
+                pmReadout *readout = inputs->data[r]; // Input readout
+                int yIn = i - readout->row0; // y position on input readout
+                int xIn = j - readout->col0; // x position on input readout
+                psImage *image = readout->image; // The readout image
+
+                #if 0
+                // This should have been taken care of already:
+                // Check bounds
+                if (xIn < 0 || xIn >= image->numCols || yIn < 0 || yIn >= image->numRows) {
+                    continue;
+                }
+                #endif
+                pixelsData[r] = image->data.F32[yIn][xIn];
+                if (zero) {
+                    pixelsData[r] -= zero->data.F32[r];
+                }
+                if (scale) {
+                    pixelsData[r] *= invScale->data.F32[r];
+                }
+
+                // Check mask
+                psImage *roMask = readout->mask; // The mask image
+                if (roMask && roMask->data.U8[yIn][xIn] & maskVal) {
+                    maskData[r] = 1;
+                    continue;
+                }
+
+                numValid++;
+            }
+
+            if (numValid == 0) {
+                outputMask[yOut][xOut] = PM_MASK_FLAT;
+                outputImage[yOut][xOut] = NAN;
+                continue;
+            }
+
+            // Apply fracLow,fracHigh if there are enough pixels
+            if (numValid * keepFrac >= params->nKeep && keepFrac != 1.0) {
+                index = psVectorSortIndex(index, pixels);
+                int numLow = numValid * params->fracLow; // Number of low pixels to clip
+                int numHigh = numValid * params->fracHigh; // Number of high pixels to clip
+                // Low pixels
+                psS32 *indexData = index->data.S32; // Dereference index
+                for (int k = 0, numMasked = 0; numMasked < numLow && k < index->n; k++) {
+                    // Don't count the ones that are already masked
+                    if (!maskData[indexData[k]]) {
+                        maskData[indexData[k]] = 1;
+                        numMasked++;
+                    }
+                }
+                // High pixels
+                for (int k = pixels->n - 1, numMasked = 0; numMasked < numHigh && k >= 0; k--) {
+                    // Don't count the ones that are already masked
+                    if (! maskData[indexData[k]]) {
+                        maskData[indexData[k]] = 1;
+                        numMasked++;
+                    }
+                }
+            }
+
+            // Combination
+            psVectorStats(stats, pixels, weights, mask, 1);
+            outputImage[yOut][xOut] = getStat(stats, params->combine);
+            if (haveWeights) {
+                float stdev = getStat(stats, combineStdev);
+                outputWeight[yOut][xOut] = PS_SQR(stdev); // Variance
+            }
+        }
+    }
+    #if 0
+    if (psTraceGetLevel(__func__) > 9) {
+        printf("\n");
+    }
+    #endif
+    psFree(index);
+    psFree(pixels);
+    psFree(mask);
+    psFree(weights);
+    psFree(stats);
+    psFree(invScale);
+
+    // Update the "concepts"
+    psList *inputCells = psListAlloc(NULL); // List of cells
+    for (long i = 0; i < inputs->n; i++) {
+        pmReadout *readout = inputs->data[i]; // Readout of interest
+        psListAdd(inputCells, PS_LIST_TAIL, readout->parent);
+    }
+    pmConceptsAverageCells(output->parent, inputCells, NULL, NULL);
+    psFree(inputCells);
+
+    return true;
+}
+
Index: /tags/RHL_v1_1/psModules/src/imcombine/pmReadoutCombine.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/imcombine/pmReadoutCombine.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/imcombine/pmReadoutCombine.h	(revision 9533)
@@ -0,0 +1,50 @@
+/** @file  pmReadoutCombine.h
+ *
+ *  This file will contain a module which will combine multiple readout images.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-06-01 02:26:32 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#if !defined(PM_READOUT_COMBINE_H)
+#define PM_READOUT_COMBINE_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pslib.h>
+#include "pmFPA.h"
+
+// Combination parameters for pmReadoutCombine.  These values define how the combination is performed, and
+// should not vary by detector, so that it can be re-used for multiple combinations.
+typedef struct
+{
+    psStatsOptions combine;             // Statistic to use when performing the combination
+    psMaskType maskVal;                 // Mask value
+    int nKeep;                          // Mimimum number of pixels to keep
+    float fracHigh;                     // Fraction of high pixels to immediately throw
+    float fracLow;                      // Fraction of low pixels to immediately throw
+    int iter;                           // Number of iterations for clipping (for CLIPPED_MEAN only)
+    float rej;                          // Rejection threshould for clipping (for CLIPPED_MEAN only)
+}
+pmCombineParams;
+
+// Allocator
+pmCombineParams *pmCombineParamsAlloc(psStatsOptions statsOptions // Statistic to use for combination
+                                     );
+
+// Combine multiple readouts, applying zero and scale, with optional minmax clipping
+bool pmReadoutCombine(pmReadout *output,// Output readout; altered and returned
+                      psArray *inputs,  // Array of input readouts (F32 image and weight, U8 mask)
+                      const psVector *zero, // Zero corrections to subtract from input, or NULL
+                      const psVector *scale, // Scale corrections to divide into input, or NULL
+                      pmCombineParams *params // Combination parameters
+                     );
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/mainpage.dox
===================================================================
--- /tags/RHL_v1_1/psModules/src/mainpage.dox	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/mainpage.dox	(revision 9533)
@@ -0,0 +1,127 @@
+/** @mainpage psModules Image Processing Library
+
+
+@section intro Introduction
+This library contains the Pan-STARRS Image Processing Pipeline (IPP) modules (psModules). These modules
+use the functionality of the Pan-STARRS Library (psLib) to perform more complex tasks associated with image
+processing. Modules were constructed to support each of the required processing stages and are listed according
+to the particular processing stage for which they will primarily be used. To preserve namespace, globally-visible
+structures and functions are prefixed with pm, an abbreviation for Pan-STARRS Modules.
+
+The capabilities provided by psModules are grouped into the following areas which are
+also reflected in the file system directory structure:
+ - Configuration
+ - Astrometry, Focal Plane
+ - Photometry
+ - Basic Image Detrending
+ - Object Detection and Classification
+ - Image Combination
+ - Image Subtraction
+
+The installed code for psModules consists of header files and a binary library.
+
+@section extinstall Required external Libraries
+
+Before building psLib from source, several external software libraries must
+be installed. These include:
+ - Pan-STARRS Library (psLib)
+     - Available from the Maui High Performance Computiong Center (MHPCC) at https://mhpcc.pan-starrs.org/code/releases
+     - Compatibility tested with rel5
+     - Note, psLib itself has many external library dependencies.
+ - Doxygen Documentation System
+     - Only needed if code documentation is to be made
+     - Available at http://www.doxygen.org
+     - Compatibility tested with version 1.3.6
+     - The optional companion package, GraphViz (a.k.a., the 'dot' command), is recommended to enable header-file dependency diagrams
+
+We recommend using the particular versions listed as compatibility tested, as
+that is the only versions of the external libraries tested to work well with psLib
+and psModules.  Though it is quite possible that later versions of the libraries
+listed will also work, care must be taken when upgrading these libraries to verify
+that its functionality is compatible with the tested version.
+
+@section install How to Build from Source
+
+Tested versions of psModules are put into a tar file and can be downloaded from:
+
+https://mhpcc.pan-starrs.org/code/releases
+
+If one has a login account on mhpcc.pan-starrs.org, direct CVS access is
+possible.  Example of the commands required for direct CVS retrieval are
+as follows:
+<pre>
+$ cvs -d:ext:USERNAME@mhpcc.pan-starrs.org:/data/panstarrs/cvsroot co -r RELEASEBRANCH psModule
+</pre>
+where:
+  - USERNAME is your login name on the server
+  - RELEASEBRANCH is the desired release branch, e.g. rel7.
+
+
+@section build How to Build and Test the psModules Library.
+
+The psModules library and associated tests are made via the GNU autoconf/automake system.
+
+The source should build using the configure script in the psModules directory.  The
+recommended steps are:
+<pre>
+$ cd psmodules
+$ ./configure
+$ make
+$ make check
+$ make install
+</pre>
+<i>Unless otherwise specified, the library is installed with PREFIX of the current directory.</i>
+
+If the code was retrieved from CVS, you will need to substitute 'autogen.sh' for 'configure' in
+above example.
+
+Other configuration options, such as location of external libraries, are also available.
+To get a list of options, type the following in the top psModules directory.
+<pre>
+$ configure --help
+</pre>
+A likely option needed is '--with-pslib-config', which specifies the location of
+the configuration script for psLib.  By default, configure searches for it using PATH, but that
+is not always sufficient.
+
+@section install How to Install
+
+To install the library using the prefix given in the configure step, execute in
+the top build directory:
+<pre>
+$ make install
+</pre>
+
+
+@section usage Building and Linking your code to the psModules library
+
+To assist the use of the library with your own code, a configuration tool is part
+of the psModules library package.  This tool, psmodules-config, is installed in the BIN
+directory, according to the options given to the configure script.
+
+The required CFLAG options for the compiler stage of code that uses psModules can be
+obtained via 'psmodules-config --cflags'.  This outputs the cc options that supplies
+include path(s) required to find the psModules headers.
+
+The required linking options, can be obtained via 'psmodules-config --libs'.  This
+outputs the ld options that supplies the library paths and files required to
+link to the psModules library.
+
+Note: psmodules-config usage above refers to the install locations of the library.  
+
+@section doc How to Create Code Documentation
+
+Both HTML and man page documentation may be generated from the inline
+documentation embedded in the code using the following commands:
+<pre>
+$ cd psmodules
+$ make docs
+</pre>
+<i>This places documentation in PREFIX/docs.</i>
+
+Also, a prebuilt set of code documentation for both the releases and last
+CVS snapshot can be found at:
+
+https://mhpcc.pan-starrs.org/docs/
+
+*/
Index: /tags/RHL_v1_1/psModules/src/objects/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/.cvsignore	(revision 9533)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/RHL_v1_1/psModules/src/objects/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/Makefile.am	(revision 9533)
@@ -0,0 +1,50 @@
+noinst_LTLIBRARIES = libpsmodulesobjects.la
+
+libpsmodulesobjects_la_CPPFLAGS = $(SRCINC) $(PSMODULES_CFLAGS) -I../pslib/
+libpsmodulesobjects_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulesobjects_la_SOURCES  = \
+     pmPeaks.c \
+     pmMoments.c \
+     pmModel.c \
+     pmModelGroup.c \
+     pmSource.c \
+     pmSourceSky.c \
+     pmSourceContour.c \
+     pmSourceFitModel.c \
+     pmSourcePhotometry.c \
+     pmSourceIO.c \
+     pmSourceIO_CMF.c \
+     pmSourceIO_CMP.c \
+     pmSourceIO_OBJ.c \
+     pmSourceIO_SX.c \
+     pmSourceIO_RAW.c \
+     pmPSF.c \
+     pmPSF_IO.c \
+     pmPSFtry.c \
+     pmGrowthCurve.c \
+     psVectorBracket.c
+
+EXTRA_DIST = \
+	models/pmModel_GAUSS.c \
+	models/pmModel_PGAUSS.c \
+	models/pmModel_QGAUSS.c \
+	models/pmModel_SGAUSS.c 
+
+pkginclude_HEADERS = \
+     pmPeaks.h \
+     pmMoments.h \
+     pmModel.h \
+     pmModelGroup.h \
+     pmSource.h \
+     pmSourceSky.h \
+     pmSourceContour.h \
+     pmSourceFitModel.h \
+     pmSourcePhotometry.h \
+     pmSourceIO.h \
+     pmPSF.h \
+     pmPSF_IO.h \
+     pmPSFtry.h \
+     pmGrowthCurve.h \
+     psVectorBracket.h
+
+CLEANFILES = *~
Index: /tags/RHL_v1_1/psModules/src/objects/models/pmModel_GAUSS.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/models/pmModel_GAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/models/pmModel_GAUSS.c	(revision 9533)
@@ -0,0 +1,168 @@
+
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+*****************************************************************************/
+
+psF32 pmModelFunc_GAUSS(psVector *deriv,
+                        const psVector *params,
+                        const psVector *x)
+{
+    psF32 X  = x->data.F32[0] - params->data.F32[2];
+    psF32 Y  = x->data.F32[1] - params->data.F32[3];
+    psF32 px = params->data.F32[4]*X;
+    psF32 py = params->data.F32[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + params->data.F32[6]*X*Y;
+    psF32 r  = exp(-z);
+    psF32 q  = params->data.F32[1]*r;
+    psF32 f  = q + params->data.F32[0];
+
+    if (deriv != NULL) {
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2*px*params->data.F32[4] + params->data.F32[6]*Y);
+        deriv->data.F32[3] = q*(2*py*params->data.F32[5] + params->data.F32[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+    }
+    return(f);
+}
+
+// define the parameter limits
+bool pmModelLimits_GAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max)
+{
+
+    *beta_lim   = psVectorAlloc (7, PS_TYPE_F32);
+    *params_min = psVectorAlloc (7, PS_TYPE_F32);
+    *params_max = psVectorAlloc (7, PS_TYPE_F32);
+    (*beta_lim)->n = (*beta_lim)->nalloc;
+    (*params_min)->n = (*params_min)->nalloc;
+    (*params_max)->n = (*params_max)->nalloc;
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 3e6;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e8;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +5.0;
+
+    return (TRUE);
+}
+
+// make an initial guess for parameters
+bool pmModelGuess_GAUSS (pmModel *model, pmSource *source)
+{
+
+    pmMoments *moments = source->moments;
+    psF32     *params  = model->params->data.F32;
+
+    params[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = moments->x;
+    params[3] = moments->y;
+    params[4] = (moments->Sx < (1.2 / 2.0)) ? 2.0 : (1.2 / moments->Sx);
+    params[5] = (moments->Sy < (1.2 / 2.0)) ? 2.0 : (1.2 / moments->Sy);
+    params[6] = 0.0;
+    return(true);
+}
+
+psF64 pmModelFlux_GAUSS(const psVector *params)
+{
+    psF64 A1   = PS_SQR(params->data.F32[4]);
+    psF64 A2   = PS_SQR(params->data.F32[5]);
+    psF64 A3   = PS_SQR(params->data.F32[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    psF64 Flux = params->data.F32[1] * Area;
+
+    return(Flux);
+}
+
+// return the radius which yields the requested flux
+// this function is never allowed to return <= 0
+psF64 pmModelRadius_GAUSS  (const psVector *params, psF64 flux)
+{
+
+    if (flux <= 0)
+        return (1.0);
+    if (params->data.F32[1] <= 0)
+        return (1.0);
+    if (flux >= params->data.F32[1])
+        return (1.0);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    return (radius);
+}
+
+// construct the PSF model from the FLT model and the psf
+bool pmModelFromPSF_GAUSS (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 7; i++) {
+        psPolynomial2D *poly = psf->params->data[i-4];
+        // XXX: be wary of a bug here.  EAM had his own version of psPolynomial2DEval().
+        // I think the reason was that hewas using nX=xOrder.  Not sure.  I changed this
+        // without verifying.
+        // out[i] = Polynomial2DEval_EAM (poly, out[2], out[3]);
+        out[i] = psPolynomial2DEval(poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+// check the status of the fitted model
+bool pmModelFitStatus_GAUSS (pmModel *model)
+{
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[4] / PAR[4]);
+    dP += PS_SQR(dPAR[5] / PAR[5]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[1] > 0);
+    status &= ((dPAR[1]/PAR[1]) < 0.5);
+
+    if (status)
+        return true;
+    return false;
+}
Index: /tags/RHL_v1_1/psModules/src/objects/models/pmModel_PGAUSS.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/models/pmModel_PGAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/models/pmModel_PGAUSS.c	(revision 9533)
@@ -0,0 +1,189 @@
+
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+*****************************************************************************/
+
+psF32 pmModelFunc_PGAUSS(psVector *deriv,
+                         const psVector *params,
+                         const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
+    psF32 t  = 1 + z + z*z/2.0;
+    psF32 r  = 1.0 / (t + z*z*z/6.0); /* exp (-Z) */
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        psF32 *dPAR = deriv->data.F32;
+        psF32 q = PAR[1]*r*r*t;
+        dPAR[0] = +1.0;
+        dPAR[1] = +r;
+        dPAR[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        dPAR[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        dPAR[4] = -2.0*q*px*X;
+        dPAR[5] = -2.0*q*py*Y;
+        dPAR[6] = -q*X*Y;
+    }
+    return(f);
+}
+
+bool pmModelLimits_PGAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max)
+{
+
+    *beta_lim   = psVectorAlloc (7, PS_TYPE_F32);
+    *params_min = psVectorAlloc (7, PS_TYPE_F32);
+    *params_max = psVectorAlloc (7, PS_TYPE_F32);
+    (*beta_lim)->n = (*beta_lim)->nalloc;
+    (*params_min)->n = (*params_min)->nalloc;
+    (*params_max)->n = (*params_max)->nalloc;
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 3e6;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e8;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +5.0;
+
+    return (TRUE);
+}
+
+// make an initial guess for parameters
+bool pmModelGuess_PGAUSS (pmModel *model, pmSource *source)
+{
+
+    pmMoments *moments = source->moments;
+    psF32     *params  = model->params->data.F32;
+
+    params[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = moments->x;
+    params[3] = moments->y;
+    params[4] = (moments->Sx < (1.2 / 2.0)) ? 2.0 : (1.2 / moments->Sx);
+    params[5] = (moments->Sy < (1.2 / 2.0)) ? 2.0 : (1.2 / moments->Sy);
+    params[6] = 0.0;
+
+    return(true);
+}
+
+psF64 pmModelFlux_PGAUSS(const psVector *params)
+{
+    float norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[4]);
+    psF64 A2   = PS_SQR(PAR[5]);
+    psF64 A3   = PS_SQR(PAR[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+
+    # define DZ 0.25
+
+    float f0 = 1.0;
+    float f1, f2;
+    for (z = DZ; z < 50; z += DZ) {
+        f1 = 1.0 / (1 + PAR[7]*z + pow(z, 2.25));
+        z += DZ;
+        f2 = 1.0 / (1 + PAR[7]*z + pow(z, 2.25));
+        norm += f0 + 4*f1 + f2;
+        f0 = f2;
+    }
+    norm *= DZ / 3.0;
+
+    psF64 Flux = PAR[1] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 pmModelRadius_PGAUSS  (const psVector *params, psF64 flux)
+{
+    if (flux <= 0)
+        return (1.0);
+    if (params->data.F32[1] <= 0)
+        return (1.0);
+    if (flux >= params->data.F32[1])
+        return (1.0);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    if (isnan(radius)) {
+        fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool pmModelFromPSF_PGAUSS (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 7; i++) {
+        psPolynomial2D *poly = psf->params->data[i-4];
+        // XXX: Verify this (from EAM change)
+        out[i] = psPolynomial2DEval(poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool pmModelFitStatus_PGAUSS (pmModel *model)
+{
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[4] / PAR[4]);
+    dP += PS_SQR(dPAR[5] / PAR[5]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[1] > 0);
+    status &= ((dPAR[1]/PAR[1]) < 0.5);
+
+    if (status)
+        return true;
+    return false;
+}
Index: /tags/RHL_v1_1/psModules/src/objects/models/pmModel_QGAUSS.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/models/pmModel_QGAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/models/pmModel_QGAUSS.c	(revision 9533)
@@ -0,0 +1,263 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z^M + z^N)
+ 
+    params->data.F32[PM_PAR_SKY] = So;
+    params->data.F32[PM_PAR_I0] = Zo;
+    params->data.F32[PM_PAR_XPOS] = Xo;
+    params->data.F32[PM_PAR_YPOS] = Yo;
+    params->data.F32[PM_PAR_SXX] = sqrt(2.0) / SigmaX;
+    params->data.F32[PM_PAR_SYY] = sqrt(2.0) / SigmaY;
+    params->data.F32[PM_PAR_SXY] = Sxy;
+    params->data.F32[PM_PAR_7] =
+    params->data.F32[PM_PAR_8] =
+*****************************************************************************/
+
+psF32 pmModelFunc_QGAUSS(psVector *deriv,
+                         const psVector *params,
+                         const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[PM_PAR_XPOS];
+    psF32 Y  = x->data.F32[1] - PAR[PM_PAR_YPOS];
+    psF32 px = PAR[PM_PAR_SXX]*X;
+    psF32 py = PAR[PM_PAR_SYY]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[PM_PAR_SXY]*X*Y;
+    if (z < 0)
+        z = 0;
+
+    psF32 zp = pow(z,1.25);
+    psF32 r  = 1.0 / (1 + PAR[PM_PAR_7]*z + z*zp);
+
+    psF32 r1 = PAR[PM_PAR_I0]*r;
+    psF32 f  = r1 + PAR[PM_PAR_SKY];
+
+    if (deriv != NULL) {
+        psF32 *dPAR = deriv->data.F32;
+
+        // note difference from a pure gaussian: q = params->data.F32[PM_PAR_I0]*r
+        psF32 t = r1*r;
+        psF32 q = t*(PAR[PM_PAR_7] + 2.25*zp);
+
+        dPAR[PM_PAR_SKY] = +1.0;
+        dPAR[PM_PAR_I0] = +r;
+        dPAR[PM_PAR_XPOS] = q*(2.0*px*PAR[PM_PAR_SXX] + PAR[PM_PAR_SXY]*Y);
+        dPAR[PM_PAR_YPOS] = q*(2.0*py*PAR[PM_PAR_SYY] + PAR[PM_PAR_SXY]*X);
+        dPAR[PM_PAR_SXX] = -2.0*q*px*X;
+        dPAR[PM_PAR_SYY] = -2.0*q*py*Y;
+        dPAR[PM_PAR_SXY] = -q*X*Y;
+        dPAR[PM_PAR_7] = -t*z;
+    }
+    return(f);
+}
+
+bool pmModelLimits_QGAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max)
+{
+
+    *beta_lim   = psVectorAlloc (8, PS_TYPE_F32);
+    *params_min = psVectorAlloc (8, PS_TYPE_F32);
+    *params_max = psVectorAlloc (8, PS_TYPE_F32);
+    (*beta_lim)->n = (*beta_lim)->nalloc;
+    (*params_min)->n = (*params_min)->nalloc;
+    (*params_max)->n = (*params_max)->nalloc;
+
+    beta_lim[0][0].data.F32[PM_PAR_SKY] = 1000;
+    beta_lim[0][0].data.F32[PM_PAR_I0] = 3e6;
+    beta_lim[0][0].data.F32[PM_PAR_XPOS] = 5;
+    beta_lim[0][0].data.F32[PM_PAR_YPOS] = 5;
+    beta_lim[0][0].data.F32[PM_PAR_SXX] = 0.5;
+    beta_lim[0][0].data.F32[PM_PAR_SYY] = 0.5;
+    beta_lim[0][0].data.F32[PM_PAR_SXY] = 0.5;
+    beta_lim[0][0].data.F32[PM_PAR_7] = 0.5;
+
+    params_min[0][0].data.F32[PM_PAR_SKY] = -1000;
+    params_min[0][0].data.F32[PM_PAR_I0] = 0;
+    params_min[0][0].data.F32[PM_PAR_XPOS] = -100;
+    params_min[0][0].data.F32[PM_PAR_YPOS] = -100;
+    params_min[0][0].data.F32[PM_PAR_SXX] = 0.01;
+    params_min[0][0].data.F32[PM_PAR_SYY] = 0.01;
+    params_min[0][0].data.F32[PM_PAR_SXY] = -5.0;
+    params_min[0][0].data.F32[PM_PAR_7] = 0.1;
+
+    params_max[0][0].data.F32[PM_PAR_SKY] = 1e5;
+    params_max[0][0].data.F32[PM_PAR_I0] = 1e8;
+    params_max[0][0].data.F32[PM_PAR_XPOS] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[PM_PAR_YPOS] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[PM_PAR_SXX] = 2.0;
+    params_max[0][0].data.F32[PM_PAR_SYY] = 2.0;
+    params_max[0][0].data.F32[PM_PAR_SXY] = +5.0;
+    params_max[0][0].data.F32[PM_PAR_7] = 10.0;
+
+    return (TRUE);
+}
+
+// make an initial guess for parameters
+bool pmModelGuess_QGAUSS (pmModel *model, pmSource *source)
+{
+
+    pmMoments *moments = source->moments;
+    pmPeak    *peak    = source->peak;
+    psF32     *params  = model->params->data.F32;
+
+    params[PM_PAR_SKY] = moments->Sky;
+    params[PM_PAR_I0] = moments->Peak - moments->Sky;
+    params[PM_PAR_XPOS] = peak->x;
+    params[PM_PAR_YPOS] = peak->y;
+    params[PM_PAR_SXX] = (moments->Sx < (1.2 / 2.0)) ? 2.0 : (1.2 / moments->Sx);
+    params[PM_PAR_SYY] = (moments->Sy < (1.2 / 2.0)) ? 2.0 : (1.2 / moments->Sy);
+    params[PM_PAR_SXY] = 0.0;
+    params[PM_PAR_7] = 1.0;
+
+    return(true);
+}
+
+psF64 pmModelFlux_QGAUSS(const psVector *params)
+{
+    float z;
+    float norm;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[PM_PAR_SXX]);
+    psF64 A2   = PS_SQR(PAR[PM_PAR_SYY]);
+    psF64 A3   = PS_SQR(PAR[PM_PAR_SXY]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+
+    # define DZ 0.25
+
+    float f0 = 1.0;
+    float f1, f2;
+    for (z = DZ; z < 50; z += DZ) {
+        f1 = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, 2.25));
+        z += DZ;
+        f2 = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, 2.25));
+        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 pmModelRadius_QGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 z, f;
+    psF32 *PAR = params->data.F32;
+    int Nstep = 0;
+
+    if (flux <= 0)
+        return (1.0);
+    if (PAR[PM_PAR_I0] <= 0)
+        return (1.0);
+    if (flux >= PAR[PM_PAR_I0])
+        return (1.0);
+
+    // if Sx == Sy, sigma = Sx == Sy
+    psF64 sigma = hypot (1.0 / PAR[PM_PAR_SXX], 1.0 / PAR[PM_PAR_SYY]) / sqrt(2.0);
+    psF64 limit = flux / PAR[PM_PAR_I0];
+
+    # if 0
+    /* test example will just use both, printing both */
+    psF64 dz = 1.0 / (2.0 * sigma*sigma);
+
+    // we can do this much better with intelligent choices here
+    for (z = 0.0; z < 30.0; z += dz) {
+        Nstep ++;
+        f = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, 2.25));
+        // test: f = 1.0 / (1 + PAR[PM_PAR_7]*z + PS_SQR(z));
+        if (f < limit)
+            break;
+    }
+    // fprintf (stderr, "rad 1: %f, want: %f, got: %f (%d steps)\n", z, limit, f, Nstep);
+
+    # else
+
+        /* use the fact that f is monotonically decreasing */
+        z = 0;
+    Nstep = 0;
+
+    // choose a z value guaranteed to be beyond our limit
+    float z0 = pow((1.0 / limit), (1.0 / 2.25));
+    float z1 = (1.0 / limit) / PAR[PM_PAR_7];
+    z1 = PS_MAX (z0, z1);
+    z0 = 0.0;
+
+    float f0 = 1.0 / (1 + PAR[PM_PAR_7]*z0 + pow(z0, 2.25));
+    float f1 = 1.0 / (1 + PAR[PM_PAR_7]*z1 + pow(z1, 2.25));
+    while ((Nstep < 10) && (fabs(z1 - z0) > 0.5)) {
+        z = 0.5*(z0 + z1);
+        f = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, 2.25));
+        // fprintf (stderr, "%f  %f  %f   :   %f  %f  %f\n", f0, f, f1, z0, z, z1);
+        if (f > limit) {
+            z0 = z;
+            f0 = f;
+        } else {
+            z1 = z;
+            f1 = f;
+        }
+        Nstep ++;
+    }
+    // fprintf (stderr, "rad 2: %f, want: %f, got: %f (%d steps)\n", z, limit, f, Nstep);
+    # endif
+
+    psF64 radius = sigma * sqrt (2.0 * z);
+    if (isnan(radius)) {
+        fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool pmModelFromPSF_QGAUSS (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    out[PM_PAR_SKY] = in[PM_PAR_SKY];
+    out[PM_PAR_I0] = in[PM_PAR_I0];
+    out[PM_PAR_XPOS] = in[PM_PAR_XPOS];
+    out[PM_PAR_YPOS] = in[PM_PAR_YPOS];
+
+    assert (PM_PAR_YPOS == 3);  // so that we copy the rest of the params
+    for (int i = 4; i < 8; i++) {
+        psPolynomial2D *poly = psf->params->data[i-4];
+        // XXX: Verify this (from EAM change)
+        //out[i] = Polynomial2DEval_EAM(poly, out[PM_PAR_XPOS], out[PM_PAR_YPOS]);
+        out[i] = psPolynomial2DEval(poly, out[PM_PAR_XPOS], out[PM_PAR_YPOS]);
+    }
+    return(true);
+}
+
+bool pmModelFitStatus_QGAUSS (pmModel *model)
+{
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[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 false;
+    return true;
+}
Index: /tags/RHL_v1_1/psModules/src/objects/models/pmModel_RGAUSS.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/models/pmModel_RGAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/models/pmModel_RGAUSS.c	(revision 9533)
@@ -0,0 +1,157 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z + z^Npow)
+ 
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = 1 / SigmaX;
+    params->data.F32[5] = 1 / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = Npow
+*****************************************************************************/
+
+psF64 psModelFunc_RGAUSS(psVector *deriv,
+                         const psVector *params,
+                         const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
+
+    // psF32 FACT = 1 + 5*exp(-5*PAR[7]);
+
+    psF32 p  = pow(z, PAR[7] - 1.0);
+    psF32 r  = 1.0 / (1 + z + z*p);
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[1]*r*r;
+        psF32 q = t*(1 + PAR[7]*p);
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -q*px*X*2;
+        deriv->data.F32[5] = -q*py*Y*2;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -5.0*t*log(z)*p*z;
+
+        // deriv->data.F32[4] = -1.8*q*px*X*2;
+        // deriv->data.F32[5] = -1.8*q*py*Y*2;
+        // deriv->data.F32[6] = -1.5*q*X*Y;
+        // deriv->data.F32[7] = -5.0*t*log(z)*p*z;
+    }
+    return(f);
+}
+
+psF64 psModelFlux_RGAUSS(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[4]);
+    psF64 A2   = PS_SQR(PAR[5]);
+    psF64 A3   = PS_SQR(PAR[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+        f = 1.0 / (1 + z + pow(z, PAR[7]));
+        norm += f;
+    }
+    norm *= 0.01;
+
+    psF64 Flux = params->data.F32[1] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 psModelRadius_RGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 z, f, p;
+    psF32 *PAR = params->data.F32;
+
+    if (flux <= 0)
+        return (1.0);
+    if (PAR[1] <= 0)
+        return (1.0);
+    if (flux >= PAR[1])
+        return (1.0);
+
+    // if Sx == Sy, sigma = Sx == Sy
+    psF64 sigma = hypot (1.0 / PAR[4], 1.0 / PAR[5]) / sqrt(2.0);
+    psF64 dz = 1.0 / (2.0 * sigma*sigma);
+    psF64 limit = flux / PAR[1];
+
+    // we can do this much better with intelligent choices here
+    for (z = 0.0; z < 20.0; z += dz) {
+        p = pow(z, PAR[7]);
+        f = 1.0 / (1 + z + p);
+        if (f < limit)
+            break;
+    }
+    psF64 radius = sigma * sqrt (2.0 * z);
+    if (isnan(radius)) {
+        fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool psModelGuess_RGAUSS (psModel *model, psSource *source)
+{
+
+    psVector *params = model->params;
+
+    psEllipseAxes axes;
+    psEllipseShape shape;
+    psEllipseMoments moments;
+
+    moments.x2 = PS_SQR(source->moments->Sx);
+    moments.y2 = PS_SQR(source->moments->Sy);
+    moments.xy = source->moments->Sxy;
+
+    axes = psEllipseMomentsToAxes(moments);
+    shape = psEllipseAxesToShape(axes);
+
+    params->data.F32[0] = source->moments->Sky;
+    params->data.F32[1] = source->peak->counts - source->moments->Sky;
+    params->data.F32[2] = source->moments->x;
+    params->data.F32[3] = source->moments->y;
+    params->data.F32[4] = 1.0 / shape.sx;
+    params->data.F32[5] = 1.0 / shape.sy;
+    params->data.F32[6] = shape.sxy;
+    params->data.F32[7] = 2.0;
+    return(true);
+}
+
+bool psModelFromPSF_RGAUSS (psModel *modelPSF, psModel *modelFLT, pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 8; i++) {
+        psPolynomial2D *poly = psf->params->data[i-4];
+        out[i] = Polynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
Index: /tags/RHL_v1_1/psModules/src/objects/models/pmModel_SGAUSS.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/models/pmModel_SGAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/models/pmModel_SGAUSS.c	(revision 9533)
@@ -0,0 +1,361 @@
+
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z^Npow + St*z^Ntide)
+ 
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = 1 / SigmaX;
+    params->data.F32[5] = 1 / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = Npow
+    params->data.F32[8] = St
+*****************************************************************************/
+
+# define SQ(A)((A)*(A))
+psF64 psImageEllipseContour (psEllipseAxes axes, double xc, double yc, psImage *image);
+psF64 p_psImageGetElementF64(psImage *a, int i, int j);
+
+psF32 pmModelFunc_SGAUSS(psVector *deriv,
+                         const psVector *params,
+                         const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = PS_MAX((0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y), 1e-8);
+    // note that if z -> 0, dPAR[7] -> -inf
+    // also z^(PAR[7]-1) -> Inf
+
+    psF32 pr = z*PAR[8];
+    psF32 pr3 = pr*pr*pr;
+    psF32 p  = pow(z, PAR[7] - 1.0);
+    psF32 r  = 1.0 / (1 + z*p + pr*pr3);
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[1]*r*r;
+        psF32 q = t*(PAR[7]*p + 4*PAR[8]*pr3);
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -2*t*log(z)*z*p;
+        deriv->data.F32[8] = -2*t*4*z*pr3;
+    }
+    return(f);
+}
+
+bool pmModelLimits_SGAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max)
+{
+
+    *beta_lim   = psVectorAlloc (9, PS_TYPE_F32);
+    *params_min = psVectorAlloc (9, PS_TYPE_F32);
+    *params_max = psVectorAlloc (9, PS_TYPE_F32);
+    (*beta_lim)->n = (*beta_lim)->nalloc;
+    (*params_min)->n = (*params_min)->nalloc;
+    (*params_max)->n = (*params_max)->nalloc;
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+    beta_lim[0][0].data.F32[7] = 0.5;
+    beta_lim[0][0].data.F32[8] = 0.05;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+    params_min[0][0].data.F32[7] = 0.5;
+    params_min[0][0].data.F32[8] = 0.001;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +3.0;
+    params_max[0][0].data.F32[7] = 5.0;
+    params_max[0][0].data.F32[8] = 0.5;
+
+    return (TRUE);
+}
+
+// measure the flux for the elliptical contour
+psF64 psImageEllipseContour (psEllipseAxes axes, double xc, double yc, psImage *image)
+{
+
+    double t, dt, ct, st, xo, yo, value;
+    int N, Nt, x, y;
+
+    // choose dt to uniformly divide contour, with ~1 pix spacing at most
+    dt = asin (1 / axes.minor);
+    Nt = (int)(2*M_PI / dt) + 1;
+    dt = 2*M_PI / Nt;
+
+    ct = cos(axes.theta);
+    st = sin(axes.theta);
+    xo = xc - image->col0;
+    yo = yc - image->row0;
+
+    psVector *contour = psVectorAlloc (Nt, PS_TYPE_F32);
+    contour->n = contour->nalloc;
+    for (t = 0, N = 0; (t < 2*M_PI) && (N < Nt); t += dt) {
+        x = ct*axes.major*cos(t) + st*axes.minor*sin(t) + xo;
+        y = ct*axes.minor*sin(t) + st*axes.major*cos(t) + yo;
+        value = p_psImageGetElementF64(image, x, y);
+        if (isfinite(value)) {
+            contour->data.F32[N] = value;
+            N++;
+        }
+    }
+    contour->n = N;
+    // accept every pixel: double counting is not so problematic here...
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
+    psVectorStats (stats, contour, NULL, NULL, 0);
+    value = stats->sampleMedian;
+
+    psFree (stats);
+    psFree (contour);
+
+    return (value);
+}
+
+bool pmModelGuess_SGAUSS (pmModel *model, pmSource *source)
+{
+
+    pmMoments *sMoments = source->moments;
+    pmPeak    *peak     = source->peak;
+    psF32     *params   = model->params->data.F32;
+
+    psEllipseAxes axes;
+    psEllipseShape shape;
+    psEllipseMoments moments;
+
+    moments.x2 = PS_SQR(sMoments->Sx);
+    moments.y2 = PS_SQR(sMoments->Sy);
+    moments.xy = sMoments->Sxy;
+
+    // solve the math to go from Moments To Shape
+    axes = psEllipseMomentsToAxes(moments);
+    shape = psEllipseAxesToShape(axes);
+
+    params[0] = sMoments->Sky;
+    params[1] = sMoments->Peak - sMoments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->y;
+    params[4] = 1.0 / shape.sx;
+    params[5] = 1.0 / shape.sy;
+    params[6] = shape.sxy;
+
+    # if (0)
+
+        f1 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+    axes.major *= 2.0;
+    axes.minor *= 2.0;
+    f2 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+
+    if (f1 > f2) {
+        params[7] = PS_MIN (3.0, PS_MAX (0.5, log(2.0*(f1/f2) - 1.0) / log(2.0)));
+    } else {
+        params[7] = 0.5;
+    }
+    # endif
+
+    params[7] = 1.8;
+    params[8] = 0.1;
+
+
+    return(true);
+}
+
+// XXX EAM : test version using flux contours to guess slope
+bool pmModelGuess_SGAUSS_HARD (pmModel *model, pmSource *source)
+{
+
+    pmMoments *sMoments = source->moments;
+    pmPeak    *peak     = source->peak;
+    psF32     *params   = model->params->data.F32;
+    float f1, f2;
+
+    psEllipseAxes axes;
+    psEllipseShape shape;
+    psEllipseMoments moments;
+
+    moments.x2 = PS_SQR(sMoments->Sx);
+    moments.y2 = PS_SQR(sMoments->Sy);
+    moments.xy = sMoments->Sxy;
+
+    // solve the math to go from Moments To Shape
+    axes = psEllipseMomentsToAxes(moments);
+    shape = psEllipseAxesToShape(axes);
+
+    params[0] = sMoments->Sky;
+    params[1] = sMoments->Peak - sMoments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->y;
+    params[4] = 1.0 / shape.sx;
+    params[5] = 1.0 / shape.sy;
+    params[6] = shape.sxy;
+
+    f1 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+    axes.major *= 2.0;
+    axes.minor *= 2.0;
+    f2 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+
+    if (f1 > f2) {
+        params[7] = PS_MIN (3.0, PS_MAX (0.5, log(2.0*(f1/f2) - 1.0) / log(2.0)));
+    } else {
+        params[7] = 0.5;
+    }
+    params[8] = 0.1;
+
+    return(true);
+}
+
+psF64 pmModelFlux_SGAUSS(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[4]);
+    psF64 A2   = PS_SQR(PAR[5]);
+    psF64 A3   = PS_SQR(PAR[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+        psF32 pr = PAR[8]*z;
+        f = 1.0 / (1 + pow(z, PAR[7]) + SQ(SQ(pr)));
+        norm += f;
+    }
+    norm *= 0.01;
+
+    psF64 Flux = PAR[1] * Area * norm;
+
+    return(Flux);
+}
+
+// XXX need to define the radius along the major axis
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 pmModelRadius_SGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 r, z = 0.0, pr, f;
+    psF32 *PAR = params->data.F32;
+
+    psEllipseAxes axes;
+    psEllipseShape shape;
+
+    if (flux <= 0)
+        return (1.0);
+    if (PAR[1] <= 0)
+        return (1.0);
+    if (flux >= PAR[1])
+        return (1.0);
+
+    // convert Sx,Sy,Sxy to major/minor axes
+    shape.sx = 1.0 / PAR[4];
+    shape.sy = 1.0 / PAR[5];
+    shape.sxy = PAR[6];
+
+    axes = psEllipseShapeToAxes (shape);
+    psF64 dr = 1.0 / axes.major;
+    psF64 limit = flux / PAR[1];
+
+    // XXX : we can do this faster with an intelligent starting choice
+    for (r = 0.0; r < 20.0; r += dr) {
+        z = SQ(r);
+        pr = PAR[8]*z;
+        f = 1.0 / (1 + pow(z, PAR[7]) + SQ(SQ(pr)));
+        if (f < limit)
+            break;
+    }
+    psF64 radius = 2.0 * axes.major * sqrt (z);
+    if (isnan(radius)) {
+        fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool pmModelFromPSF_SGAUSS (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 9; i++) {
+        psPolynomial2D *poly = psf->params->data[i-4];
+        // XXX: Verify this (from EAM change)
+        //out[i] = Polynomial2DEval_EAM(poly, out[2], out[3]);
+        out[i] = psPolynomial2DEval(poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool pmModelFitStatus_SGAUSS (pmModel *model)
+{
+
+    psF32 dP;
+    bool  status;
+    psEllipseAxes axes;
+    psEllipseShape shape;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    shape.sx = 1.0 / PAR[4];
+    shape.sy = 1.0 / PAR[5];
+    shape.sxy = PAR[6];
+
+    axes = psEllipseShapeToAxes (shape);
+
+    dP = 0;
+    dP += PS_SQR(dPAR[4] / PAR[4]);
+    dP += PS_SQR(dPAR[5] / PAR[5]);
+    dP += PS_SQR(dPAR[7] / PAR[7]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[1] > 0);
+    status &= ((dPAR[1]/PAR[1]) < 0.5);
+    status &= (fabs(PAR[8]) < 0.5);
+    status &= (dPAR[8] < 0.1);
+    status &= (axes.major > 1.41);
+    status &= (axes.minor > 1.41);
+    status &= ((axes.major / axes.minor) < 5.0);
+    status &= (PAR[7] > 0.5);
+
+    if (status)
+        return true;
+    return false;
+}
Index: /tags/RHL_v1_1/psModules/src/objects/models/pmModel_TGAUSS.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/models/pmModel_TGAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/models/pmModel_TGAUSS.c	(revision 9533)
@@ -0,0 +1,114 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z^M + z^N)
+ 
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = 
+*****************************************************************************/
+
+psF64 psModelFunc_TGAUSS(psVector *deriv,
+                         const psVector *params,
+                         const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
+
+    psF32 p  = pow(z, 1.2);
+    psF32 r  = 1.0 / (1 + z + PAR[7]*z*p);
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[1]*r*r;
+        psF32 q = t*(1 + PAR[7]*2.2*p);
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -t*z*p;
+    }
+    return(f);
+}
+
+psF64 psModelFlux_TGAUSS(const psVector *params)
+{
+    psF64 A1   = 1 / PS_SQR(params->data.F32[4]);
+    psF64 A2   = 1 / PS_SQR(params->data.F32[5]);
+    psF64 A3   = params->data.F32[6];
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - PS_SQR(A3));
+    // Area is equivalent to 2 pi sigma^2
+
+    psF64 Flux = params->data.F32[1] * Area;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 psModelRadius_TGAUSS  (const psVector *params, psF64 flux)
+{
+    if (flux <= 0)
+        return (1.0);
+    if (params->data.F32[1] <= 0)
+        return (1.0);
+    if (flux >= params->data.F32[1])
+        return (1.0);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    if (isnan(radius)) {
+        fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool psModelGuess_TGAUSS (psModel *model, psSource *source)
+{
+
+    psVector *params = model->params;
+
+    params->data.F32[0] = source->moments->Sky;
+    params->data.F32[1] = source->peak->counts - source->moments->Sky;
+    params->data.F32[2] = source->moments->x;
+    params->data.F32[3] = source->moments->y;
+    params->data.F32[4] = 1.0/source->moments->Sx;
+    params->data.F32[5] = 1.0/source->moments->Sy;
+    // params->data.F32[6] = source->moments->Sxy;
+    params->data.F32[6] = 0.0;
+    params->data.F32[7] = 5.0;
+    return(true);
+}
+
+bool psModelFromPSF_TGAUSS (psModel *modelPSF, psModel *modelFLT, pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 8; i++) {
+        psPolynomial2D *poly = psf->params->data[i-4];
+        out[i] = Polynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
Index: /tags/RHL_v1_1/psModules/src/objects/models/pmModel_WAUSS.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/models/pmModel_WAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/models/pmModel_WAUSS.c	(revision 9533)
@@ -0,0 +1,105 @@
+
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+*****************************************************************************/
+
+psF64 psModelFunc_WAUSS(psVector *deriv,
+                        const psVector *params,
+                        const psVector *x)
+{
+    psF32 X = x->data.F32[0] - params->data.F32[2];
+    psF32 Y = x->data.F32[1] - params->data.F32[2];
+    psF32 px = params->data.F32[4]*X;
+    psF32 py = params->data.F32[5]*Y;
+    psF32 z = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + params->data.F32[6]*X*Y;
+    psF32 t = 0.5*z*z*(1.0 + params->data.F32[8]*z/3.0);
+    psF32 r = 1.0 / (1.0 + z + params->data.F32[7]*t); /* exp (-Z) */
+    psF32 f = params->data.F32[1]*r + params->data.F32[0];
+
+    if (deriv != NULL) {
+        // note difference from gaussian: q = params->data.F32[1]*r
+        psF32 q = params->data.F32[1]*r*r*(1.0 + params->data.F32[7]*z*(1.0 + params->data.F32[8]*z/2.0));
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*params->data.F32[4] + params->data.F32[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*params->data.F32[5] + params->data.F32[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -100.0*params->data.F32[1]*r*r*t;
+        deriv->data.F32[8] = -100.0*params->data.F32[1]*r*r*params->data.F32[7]*(z*z*z)/6.0;
+        // The values of 100 dampen the swing of params->data.F32[7,8] */
+    }
+    return(f);
+}
+
+// this is probably wrong since it uses the gauss integral 2 pi sigma^2
+psF64 psModelFlux_WAUSS(const psVector *params)
+{
+    psF64 A1   = 1 / PS_SQR(params->data.F32[4]);
+    psF64 A2   = 1 / PS_SQR(params->data.F32[5]);
+    psF64 A3   = params->data.F32[6];
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - PS_SQR(A3));
+    // Area is equivalent to 2 pi sigma^2
+
+    psF64 Flux = params->data.F32[1] * Area;
+
+    return(Flux);
+}
+
+// return the radius which yields the requested flux
+psF64 psModelRadius_WAUSS  (const psVector *params, psF64 flux)
+{
+    if (flux <= 0)
+        return (1.0);
+    if (params->data.F32[1] <= 0)
+        return (1.0);
+    if (flux >= params->data.F32[1] <= 0)
+        return (1.0);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    return (radius);
+}
+
+bool psModelGuess_WAUSS (psModel *model, psSource *source)
+{
+
+    psVector *params = model->params;
+
+    params->data.F32[0] = source->moments->Sky;
+    params->data.F32[1] = source->peak->counts - source->moments->Sky;
+    params->data.F32[2] = source->moments->x;
+    params->data.F32[3] = source->moments->y;
+    params->data.F32[4] = sqrt(2.0) / source->moments->Sx;
+    params->data.F32[5] = sqrt(2.0) / source->moments->Sy;
+    params->data.F32[6] = source->moments->Sxy;
+    // XXX: What are these?
+    // params->data.F32[7] = B2;
+    // params->data.F32[8] = B3;
+    return(true);
+}
+
+bool psModelFromPSF_WAUSS (psModel *modelPSF, psModel *modelFLT, pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 9; i++) {
+        psPolynomial2D *poly = psf->params->data[i-4];
+        out[i] = Polynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
Index: /tags/RHL_v1_1/psModules/src/objects/models/pmModel_ZGAUSS.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/models/pmModel_ZGAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/models/pmModel_ZGAUSS.c	(revision 9533)
@@ -0,0 +1,164 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z^Npow + PAR8*z^4)
+ 
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = 1 / SigmaX;
+    params->data.F32[5] = 1 / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = Npow
+*****************************************************************************/
+
+# define SQ(A)((A)*(A))
+# define PAR8 0.1
+
+psF64 psModelFunc_ZGAUSS(psVector *deriv,
+                         const psVector *params,
+                         const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
+
+    psF32 pr = PAR8*z;
+    psF32 p  = pow(z, PAR[7] - 1.0);
+    psF32 r  = 1.0 / (1 + z*p + SQ(SQ(pr)));
+    psF32 f  = PAR[1]*r + PAR[0];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[1]*r*r;
+        psF32 q = t*(PAR[7]*p + 4*PAR8*pr*pr*pr);
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[4] + PAR[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[5] + PAR[6]*X);
+        deriv->data.F32[4] = -q*px*X;
+        deriv->data.F32[5] = -q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -t*log(z)*z*p;
+    }
+    return(f);
+}
+
+psF64 psModelFlux_ZGAUSS(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[4]);
+    psF64 A2   = PS_SQR(PAR[5]);
+    psF64 A3   = PS_SQR(PAR[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    psF32 pr = PAR8*z;
+    for (z = 0.005; z < 50; z += 0.01) {
+        f = 1.0 / (1 + pow(z, PAR[7]) + SQ(SQ(pr)));
+        norm += f;
+    }
+    norm *= 0.01;
+
+    psF64 Flux = PAR[1] * Area * norm;
+
+    return(Flux);
+}
+
+// XXX need to define the radius along the major axis
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 psModelRadius_ZGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 r, z, pr, f;
+    psF32 *PAR = params->data.F32;
+
+    psEllipseAxes axes;
+    psEllipseShape shape;
+
+    if (flux <= 0)
+        return (1.0);
+    if (PAR[1] <= 0)
+        return (1.0);
+    if (flux >= PAR[1])
+        return (1.0);
+
+    // convert Sx,Sy,Sxy to major/minor axes
+    shape.sx = 1.0 / PAR[4];
+    shape.sy = 1.0 / PAR[5];
+    shape.sxy = PAR[6];
+
+    axes = psEllipseShapeToAxes (shape);
+    psF64 dr = 1.0 / axes.major;
+    psF64 limit = flux / PAR[1];
+
+    // XXX : we can do this faster with an intelligent starting choice
+    for (r = 0.0; r < 20.0; r += dr) {
+        z = SQ(r);
+        pr = PAR8*z;
+        f = 1.0 / (1 + pow(z, PAR[7]) + SQ(SQ(pr)));
+        if (f < limit)
+            break;
+    }
+    psF64 radius = 2.0 * axes.major * sqrt (z);
+    if (isnan(radius)) {
+        fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool psModelGuess_ZGAUSS (psModel *model, psSource *source)
+{
+
+    psVector *params = model->params;
+
+    psEllipseAxes axes;
+    psEllipseShape shape;
+    psEllipseMoments moments;
+
+    moments.x2 = PS_SQR(source->moments->Sx);
+    moments.y2 = PS_SQR(source->moments->Sy);
+    moments.xy = source->moments->Sxy;
+
+    axes = psEllipseMomentsToAxes(moments);
+    shape = psEllipseAxesToShape(axes);
+
+    params->data.F32[0] = source->moments->Sky;
+    params->data.F32[1] = source->peak->counts - source->moments->Sky;
+    params->data.F32[2] = source->moments->x;
+    params->data.F32[3] = source->moments->y;
+    params->data.F32[4] = 1.0 / shape.sx;
+    params->data.F32[5] = 1.0 / shape.sy;
+    params->data.F32[6] = shape.sxy;
+    params->data.F32[7] = 1.9;
+    return(true);
+}
+
+bool psModelFromPSF_ZGAUSS (psModel *modelPSF, psModel *modelFLT, pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 8; i++) {
+        psPolynomial2D *poly = psf->params->data[i-4];
+        out[i] = Polynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
Index: /tags/RHL_v1_1/psModules/src/objects/pmGrowthCurve.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmGrowthCurve.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmGrowthCurve.c	(revision 9533)
@@ -0,0 +1,59 @@
+/** @file  pmGrowthCurve.c
+ *
+ *  Measure the curve-of-growth for sources
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-22 21:35:34 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pslib.h>
+#include "pmGrowthCurve.h"
+#include "psVectorBracket.h"
+
+static void pmGrowthCurveFree (pmGrowthCurve *growth)
+{
+
+    if (growth == NULL)
+        return;
+
+    psFree (growth->radius);
+    psFree (growth->apMag);
+    return;
+}
+
+pmGrowthCurve *pmGrowthCurveAlloc (psF32 minRadius, psF32 maxRadius, psF32 refRadius)
+{
+
+    pmGrowthCurve *growth = psAlloc (sizeof(pmGrowthCurve));
+    psMemSetDeallocator(growth, (psFreeFunc) pmGrowthCurveFree);
+
+    // fractional pixel radii are not well defined; use integer steps
+    growth->radius = psVectorCreate (NULL, minRadius, maxRadius, 1.0, PS_TYPE_F32);
+    growth->apMag  = psVectorAlloc (growth->radius->n, PS_TYPE_F32);
+    growth->apMag->n = growth->radius->n;
+
+    // XXX may want to extend this to allow for a different refRadius;
+    growth->refRadius = refRadius;
+    growth->maxRadius = maxRadius;
+    growth->apLoss = 0.0;
+    growth->fitMag = 0.0;
+    return growth;
+}
+
+psF32 pmGrowthCurveCorrect (pmGrowthCurve *growth, psF32 radius)
+{
+
+    float apRad = psVectorInterpolate (growth->radius, growth->apMag, radius);
+    float apCor = growth->apRef - apRad;
+    return apCor;
+}
+
Index: /tags/RHL_v1_1/psModules/src/objects/pmGrowthCurve.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmGrowthCurve.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmGrowthCurve.h	(revision 9533)
@@ -0,0 +1,20 @@
+
+# ifndef PM_GROWTH_CURVE_H
+# define PM_GROWTH_CURVE_H
+
+typedef struct
+{
+    psVector *radius;
+    psVector *apMag;
+    psF32 refRadius;
+    psF32 maxRadius;
+    psF32 fitMag;
+    psF32 apRef;   // apMag[refRadius]
+    psF32 apLoss;  // fitMag - apRef
+}
+pmGrowthCurve;
+
+pmGrowthCurve *pmGrowthCurveAlloc (psF32 minRadius, psF32 maxRadius, psF32 refRadius);
+psF32 pmGrowthCurveCorrect (pmGrowthCurve *growth, psF32 radius);
+
+# endif /* PM_GROWTH_CURVE_H */
Index: /tags/RHL_v1_1/psModules/src/objects/pmModel.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmModel.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmModel.c	(revision 9533)
@@ -0,0 +1,232 @@
+/** @file  pmModel.c
+ *
+ *  Functions to define and manipulate object models
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-15 09:49:01 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmModel.h"
+
+/* The following types and functions need to be known by pmModel.c and are defined in
+   pmModelGroup.h.  The use of pmSource and other types in pmModelGroup.h prevent us from
+   directly including it here  ***/
+
+typedef psMinimizeLMChi2Func pmModelFunc;
+typedef bool (*pmModelFitStatusFunc)(pmModel *model);
+pmModelFunc pmModelFunc_GetFunction (pmModelType type);
+pmModelFitStatusFunc pmModelFitStatusFunc_GetFunction (pmModelType type);
+int pmModelParameterCount(pmModelType type);
+
+static void modelFree(pmModel *tmp)
+{
+    psTrace("psModules.objects", 4, "---- %s() begin ----\n", __func__);
+    psFree(tmp->params);
+    psFree(tmp->dparams);
+    psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+}
+
+/******************************************************************************
+pmModelAlloc(): Allocate the pmModel structure, along with its parameters,
+and initialize the type member.  Initialize the params to 0.0.
+XXX EAM: simplifying code with pmModelParameterCount
+*****************************************************************************/
+pmModel *pmModelAlloc(pmModelType type)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    pmModel *tmp = (pmModel *) psAlloc(sizeof(pmModel));
+
+    tmp->type = type;
+    tmp->chisq = 0.0;
+    tmp->nIter = 0;
+    tmp->radiusTMP = 0;
+    tmp->status = PM_MODEL_UNTRIED;
+
+    psS32 Nparams = pmModelParameterCount(type);
+    if (Nparams == 0) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return(NULL);
+    }
+
+    tmp->params  = psVectorAlloc(Nparams, PS_TYPE_F32);
+    tmp->dparams = psVectorAlloc(Nparams, PS_TYPE_F32);
+    tmp->params->n = tmp->dparams->n = Nparams;
+
+    for (psS32 i = 0; i < tmp->params->n; i++) {
+        tmp->params->data.F32[i] = 0.0;
+        tmp->dparams->data.F32[i] = 0.0;
+    }
+
+    psMemSetDeallocator(tmp, (psFreeFunc) modelFree);
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(tmp);
+}
+
+// copy model to a new structure
+pmModel *pmModelCopy (pmModel *model)
+{
+
+    pmModel *new = pmModelAlloc (model->type);
+
+    new->chisq  = model->chisq;
+    new->nDOF   = model->nDOF;
+    new->nIter  = model->nIter;
+    new->status = model->status;
+    new->radiusTMP = model->radiusTMP;
+
+    for (int i = 0; i < new->params->n; i++) {
+        new->params->data.F32[i]  = model->params->data.F32[i];
+        new->dparams->data.F32[i] = model->dparams->data.F32[i];
+    }
+
+    return (new);
+}
+
+/******************************************************************************
+    pmModelEval(source, level, row): evaluates the model function at the specified coords.
+ 
+    NOTE: The coords are in subImage source->pixel coords, not image coords.
+ 
+    XXX: Use static vectors for x (NO: needs to be thread safe)
+*****************************************************************************/
+psF32 pmModelEval(pmModel *model, psImage *image, psS32 col, psS32 row)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(image, false);
+    PS_ASSERT_PTR_NON_NULL(model, false);
+    PS_ASSERT_PTR_NON_NULL(model->params, false);
+
+    // Allocate the x coordinate structure and convert row/col to image space.
+    //
+    psVector *x = psVectorAlloc(2, PS_TYPE_F32);
+    x->data.F32[0] = (psF32) (col + image->col0);
+    x->data.F32[1] = (psF32) (row + image->row0);
+    x->n = 2;
+    psF32 tmpF;
+    pmModelFunc modelFunc;
+
+    modelFunc = pmModelFunc_GetFunction (model->type);
+    tmpF = modelFunc (NULL, model->params, x);
+    psFree(x);
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(tmpF);
+}
+
+bool AddOrSubModel(psImage *image,
+                   psImage *mask,
+                   pmModel *model,
+                   bool center,
+                   bool sky,
+                   bool add
+                      )
+{
+    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);
+    x->n = 2;
+    psVector *params = model->params;
+    pmModelFunc modelFunc = pmModelFunc_GetFunction (model->type);
+    psS32 imageCol;
+    psS32 imageRow;
+    psF32 skyValue = params->data.F32[0];
+    psF32 pixelValue;
+
+    for (psS32 i = 0; i < image->numRows; i++) {
+        for (psS32 j = 0; j < image->numCols; j++) {
+            if ((mask != NULL) && mask->data.U8[i][j])
+                continue;
+
+            // Convert i/j to image coord space:
+            // 'center' option changes meaning of i,j:
+            if (center) {
+                imageCol = j - 0.5*image->numCols + model->params->data.F32[2];
+                imageRow = i - 0.5*image->numRows + model->params->data.F32[3];
+            } else {
+                imageCol = j + image->col0;
+                imageRow = i + image->row0;
+            }
+
+            x->data.F32[0] = (float) imageCol;
+            x->data.F32[1] = (float) imageRow;
+
+            // set the appropriate pixel value for this coordinate
+            if (sky) {
+                pixelValue = modelFunc (NULL, params, x);
+            } else {
+                pixelValue = modelFunc (NULL, params, x) - skyValue;
+            }
+
+
+            // add or subtract the value
+            if (add
+               ) {
+                image->data.F32[i][j] += pixelValue;
+            }
+            else {
+                image->data.F32[i][j] -= pixelValue;
+            }
+        }
+    }
+    psFree(x);
+    psTrace("psModules.objects", 3, "---- %s(true) end ----\n", __func__);
+    return(true);
+}
+
+/******************************************************************************
+ *****************************************************************************/
+bool pmModelAdd(psImage *image,
+                psImage *mask,
+                pmModel *model,
+                bool center,
+                bool sky)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    psBool rc = AddOrSubModel(image, mask, model, center, sky, true);
+    psTrace("psModules.objects", 3, "---- %s(%d) end ----\n", __func__, rc);
+    return(rc);
+}
+
+/******************************************************************************
+ *****************************************************************************/
+bool pmModelSub(psImage *image,
+                psImage *mask,
+                pmModel *model,
+                bool center,
+                bool sky)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    psBool rc = AddOrSubModel(image, mask, model, center, sky, false);
+    psTrace("psModules.objects", 3, "---- %s(%d) end ----\n", __func__, rc);
+    return(rc);
+}
+
+// check for success of model fit
+bool pmModelFitStatus (pmModel *model)
+{
+
+    bool status;
+
+    pmModelFitStatusFunc statusFunc = pmModelFitStatusFunc_GetFunction (model->type);
+    status = statusFunc (model);
+
+    return (status);
+}
+
Index: /tags/RHL_v1_1/psModules/src/objects/pmModel.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmModel.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmModel.h	(revision 9533)
@@ -0,0 +1,127 @@
+/** @file  pmObjects.h
+ *
+ *  Functions to define and manipulate object models
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-10-13 02:29:14 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+# ifndef PM_MODEL_H
+# define PM_MODEL_H
+
+// type of model carried by the pmModel structure
+typedef int pmModelType;
+
+typedef enum {
+    PM_MODEL_UNTRIED,   ///< model fit not yet attempted
+    PM_MODEL_SUCCESS,   ///< model fit succeeded
+    PM_MODEL_NONCONVERGE,  ///< model fit did not converge
+    PM_MODEL_OFFIMAGE,   ///< model fit drove out of range
+    PM_MODEL_BADARGS   ///< model fit called with invalid args
+} pmModelStatus;
+
+/** pmModel data structure
+ *
+ * Every source may have two types of models: a PSF model and a EXT (extended-source)
+ * model. The PSF model represents the best fit of the image PSF to the specific
+ * object. In this case, the PSF-dependent parameters are specified for the
+ * object by the PSF, not by the fit. The EXT model represents the best fit of
+ * the given model to the object, with all shape parameters floating in the fit.
+ *
+ */
+typedef struct
+{
+    pmModelType type;   ///< Model to be used.
+    psVector *params;   ///< Paramater values.
+    psVector *dparams;   ///< Parameter errors.
+    float chisq;   ///< Fit chi-squared.
+    float chisqNorm;   ///< re-normalized fit chi-squared.
+    int nDOF;    ///< number of degrees of freedom
+    int nIter;    ///< number of iterations to reach min
+    pmModelStatus status;  ///< fit status
+    float radiusTMP;   ///< fit radius actually used
+}
+pmModel;
+
+/** Symbolic names for the elements of [d]params
+ * Note: these are #defines not enums as a given element of [d]params
+ * may/will correspond to different parameters in different contexts
+ */
+#define PM_PAR_SKY 0   ///< Sky
+#define PM_PAR_I0 1   ///< Central intensity
+#define PM_PAR_XPOS 2   ///< X centre of object
+#define PM_PAR_YPOS 3   ///< Y centre of object
+#define PM_PAR_SXX 4   ///< shape X^2 moment
+#define PM_PAR_SYY 5   ///< shape Y^2 moment
+#define PM_PAR_SXY 6   ///< shape XY moment
+#define PM_PAR_7 7   ///< ??? Unknown parameter
+#define PM_PAR_8 8   ///< ??? Unknown parameter
+
+/** pmModelAlloc()
+ *
+ */
+pmModel *pmModelAlloc(pmModelType type);
+
+// copy model to a new structure
+pmModel *pmModelCopy (pmModel *model);
+
+psF32 pmModelEval(pmModel *model, psImage *image, psS32 col, psS32 row);
+
+/** pmModelAdd()
+ *
+ * Add the given source model flux to/from the provided image. The boolean
+ * option center selects if the source is re-centered to the image center or if
+ * it is placed at its centroid location. The boolean option sky selects if the
+ * background sky is applied (TRUE) or not. The pixel range in the target image
+ * is at most the pixel range specified by the source.pixels image. The success
+ * status is returned.
+ *
+ */
+bool pmModelAdd(
+    psImage *image,   ///< The output image (float)
+    psImage *mask,   ///< The image pixel mask (valid == 0)
+    pmModel *model,   ///< The input pmModel
+    bool center,   ///< A boolean flag that determines whether pixels are centered
+    bool sky    ///< A boolean flag that determines if the sky is subtracted
+);
+
+
+/** pmModelSub()
+ *
+ * Subtract the given source model flux to/from the provided image. The
+ * boolean option center selects if the source is re-centered to the image center
+ * or if it is placed at its centroid location. The boolean option sky selects if
+ * the background sky is applied (TRUE) or not. The pixel range in the target
+ * image is at most the pixel range specified by the source.pixels image. The
+ * success status is returned.
+ *
+ */
+bool pmModelSub(
+    psImage *image,   ///< The output image (float)
+    psImage *mask,   ///< The image pixel mask (valid == 0)
+    pmModel *model,   ///< The input pmModel
+    bool center,   ///< A boolean flag that determines whether pixels are centered
+    bool sky    ///< A boolean flag that determines if the sky is subtracted
+);
+
+/** pmModelFitStatus()
+ *
+ * This function wraps the call to the model-specific function returned by
+ * pmModelFitStatusFunc_GetFunction.  The model-specific function examines the
+ * model parameters, parameter errors, Chisq, S/N, and other parameters available
+ * from model to decide if the particular fit was successful or not.
+ *
+ * XXX: Must code this.
+ *
+ */
+bool pmModelFitStatus(
+    pmModel *model                      ///< Model to be used
+);
+
+# endif /* PM_MODEL_H */
Index: /tags/RHL_v1_1/psModules/src/objects/pmModelGroup.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmModelGroup.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmModelGroup.c	(revision 9533)
@@ -0,0 +1,220 @@
+/** @file  pmModelGroup.c
+ *
+ *  Functions to define and manipulate object model attributes
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-15 09:49:01 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmModel.h"
+#include "pmPSF.h"
+#include "pmSource.h"
+#include "pmModelGroup.h"
+
+// XXX shouldn't these be defined for us in pslib.h ???
+double hypot(double x, double y);
+double sqrt (double x);
+
+#include "models/pmModel_GAUSS.c"
+#include "models/pmModel_PGAUSS.c"
+#include "models/pmModel_QGAUSS.c"
+#include "models/pmModel_SGAUSS.c"
+
+static pmModelGroup defaultModels[] = {
+                                          {"PS_MODEL_GAUSS",        7, pmModelFunc_GAUSS,   pmModelFlux_GAUSS,   pmModelRadius_GAUSS,   pmModelLimits_GAUSS,   pmModelGuess_GAUSS,  pmModelFromPSF_GAUSS,  pmModelFitStatus_GAUSS},
+                                          {"PS_MODEL_PGAUSS",       7, pmModelFunc_PGAUSS,  pmModelFlux_PGAUSS,  pmModelRadius_PGAUSS,  pmModelLimits_PGAUSS,  pmModelGuess_PGAUSS, pmModelFromPSF_PGAUSS, pmModelFitStatus_PGAUSS},
+                                          {"PS_MODEL_QGAUSS",       8, pmModelFunc_QGAUSS,  pmModelFlux_QGAUSS,  pmModelRadius_QGAUSS,  pmModelLimits_QGAUSS,  pmModelGuess_QGAUSS, pmModelFromPSF_QGAUSS, pmModelFitStatus_QGAUSS},
+                                          {"PS_MODEL_SGAUSS",       9, pmModelFunc_SGAUSS,  pmModelFlux_SGAUSS,  pmModelRadius_SGAUSS,  pmModelLimits_SGAUSS,  pmModelGuess_SGAUSS, pmModelFromPSF_SGAUSS, pmModelFitStatus_SGAUSS},
+                                      };
+
+static pmModelGroup *models = NULL;
+static int Nmodels = 0;
+
+static void ModelGroupFree (pmModelGroup *modelGroup)
+{
+
+    if (modelGroup == NULL)
+        return;
+    return;
+}
+
+pmModelGroup *pmModelGroupAlloc (int nModels)
+{
+
+    pmModelGroup *modelGroup = (pmModelGroup *) psAlloc (nModels * sizeof(pmModelGroup));
+    psMemSetDeallocator(modelGroup, (psFreeFunc) ModelGroupFree);
+    return (modelGroup);
+}
+
+void pmModelGroupAdd (pmModelGroup *model)
+{
+
+    if (models == NULL) {
+        pmModelGroupInit ();
+    }
+
+    Nmodels ++;
+    models = (pmModelGroup *) psRealloc (models, Nmodels*sizeof(pmModelGroup));
+    models[Nmodels-1] = model[0];
+    return;
+}
+
+bool pmModelGroupInit (void)
+{
+
+    // if we do not need to init, return false;
+    if (models != NULL)
+        return false;
+
+    int Nnew = sizeof (defaultModels) / sizeof (pmModelGroup);
+
+    models = pmModelGroupAlloc (Nnew);
+    for (int i = 0; i < Nnew; i++) {
+        models[i] = defaultModels[i];
+    }
+    Nmodels = Nnew;
+    return true;
+}
+
+void pmModelGroupCleanup (void)
+{
+    psFree (models);
+    models = NULL;
+    return;
+}
+
+pmModelFunc pmModelFunc_GetFunction (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelFunc);
+}
+
+pmModelFlux pmModelFlux_GetFunction (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelFlux);
+}
+
+pmModelRadius pmModelRadius_GetFunction (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelRadius);
+}
+
+pmModelLimits pmModelLimits_GetFunction (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelLimits);
+}
+
+pmModelGuessFunc pmModelGuessFunc_GetFunction (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelGuessFunc);
+}
+
+pmModelFitStatusFunc pmModelFitStatusFunc_GetFunction (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelFitStatusFunc);
+}
+
+pmModelFromPSFFunc pmModelFromPSFFunc_GetFunction (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].modelFromPSFFunc);
+}
+
+psS32 pmModelParameterCount (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (0);
+    }
+    return (models[type].nParams);
+}
+
+psS32 pmModelSetType (char *name)
+{
+    for (int i = 0; i < Nmodels; i++) {
+        if (!strcmp(models[i].name, name)) {
+            return (i);
+        }
+    }
+    return (-1);
+}
+
+char *pmModelGetType (pmModelType type)
+{
+    if ((type < 0) || (type >= Nmodels)) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return (NULL);
+    }
+    return (models[type].name);
+}
+
+/******************************************************************************
+    pmSourceModelGuess(source, model): This function allocates a new
+    pmModel structure based on the given modelType specified in the argument list.
+    The corresponding pmModelGuess function is returned, and used to
+    supply the values of the params array in the pmModel structure.
+ 
+    XXX: Many parameters are based on the src->moments structure, which is in
+    image, not subImage coords.  Therefore, the calls to the model evaluation
+    functions will be in image, not subImage coords.  Remember this.
+*****************************************************************************/
+pmModel *pmSourceModelGuess(pmSource *source,
+                            pmModelType modelType)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source->moments, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+
+    pmModel *model = pmModelAlloc(modelType);
+
+    pmModelGuessFunc modelGuessFunc = pmModelGuessFunc_GetFunction(modelType);
+    modelGuessFunc(model, source);
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(model);
+}
+
Index: /tags/RHL_v1_1/psModules/src/objects/pmModelGroup.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmModelGroup.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmModelGroup.h	(revision 9533)
@@ -0,0 +1,199 @@
+/** @file  pmModelGroup.h
+ *
+ *  The object model function types are defined to allow for the flexible addition
+ *  of new object models. Every object model, with parameters represented by
+ *  pmModel, has an associated set of functions which provide necessary support
+ *  operations. A set of abstract functions allow the programmer to select the
+ *  approriate function or property for a specific named object model.
+ * 
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-06-10 02:58:43 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ * 
+ */
+
+# ifndef PM_MODEL_GROUP_H
+# define PM_MODEL_GROUP_H
+
+//  This function is the model chi-square minimization function for this model.
+typedef psMinimizeLMChi2Func pmModelFunc;
+
+// This function returns the integrated flux for the given model parameters.
+typedef psF64 (*pmModelFlux)(const psVector *params);
+
+
+// This function returns the radius at which the given model and parameters
+// achieves the given flux.
+typedef psF64 (*pmModelRadius)(const psVector *params, double flux);
+
+/*  This function sets the model parameter limits vectors for the given model
+ */
+typedef bool (*pmModelLimits)(psVector **beta_lim, psVector **params_min, psVector **params_max);
+
+/*  This function provides the model guess parameters based on the details of
+ *   the given source.
+ */
+typedef bool (*pmModelGuessFunc)(pmModel *model, pmSource *source);
+
+
+/*  This function constructs the PSF model for the given source based on the
+ *  supplied psf and the EXT model for the object.
+ */
+typedef bool (*pmModelFromPSFFunc)(pmModel *modelPSF, pmModel *modelEXT, pmPSF *psf);
+
+/*  This function returns the success / failure status of the given model fit
+ */
+typedef bool (*pmModelFitStatusFunc)(pmModel *model);
+
+/* Every model instance belongs to a class of models, defined by the value of
+ * the pmModelType type entry. Various functions need access to information about
+ * each of the models. Some of this information varies from model to model, and
+ * may depend on the current parameter values or other data quantities. In order
+ * to keep the code from requiring the information about each model to be coded
+ * into the low-level fitting routines, we define a collection of functions which
+ * allow us to abstract this type of model-dependent information. These generic
+ * functions take the model type and return the corresponding function pointer
+ * for the specified model. Each model is defined by creating this collection of
+ * specific functions, and placing them in a single file for each model. We
+ * define the following structure to carry the collection of information about
+ * the models.
+ */
+typedef struct
+{
+    char *name;
+    int nParams;
+    pmModelFunc          modelFunc;
+    pmModelFlux          modelFlux;
+    pmModelRadius        modelRadius;
+    pmModelLimits        modelLimits;
+    pmModelGuessFunc     modelGuessFunc;
+    pmModelFromPSFFunc   modelFromPSFFunc;
+    pmModelFitStatusFunc modelFitStatusFunc;
+}
+pmModelGroup;
+
+// allocate a pmModelGroup to hold nModels entries
+pmModelGroup *pmModelGroupAlloc (int nModels);
+
+// initialize the internal (static) model group with the default models
+bool pmModelGroupInit (void);
+
+// free the internal (static) model group
+void pmModelGroupCleanup (void);
+
+// add a new model to the internal (static) model group
+void pmModelGroupAdd (pmModelGroup *model);
+
+/* This function returns the number of parameters used by the listed function.
+ */
+int pmModelParameterCount(
+    pmModelType type                    ///< Add comment.
+);
+
+
+/* This function returns the user-space model names for the specified model type.
+ */
+char *pmModelGetType(
+    pmModelType type                    ///< Add comment.
+);
+
+
+/**
+ * 
+ * This function returns the internal model type code for the user-space model names.
+ * 
+ */
+pmModelType pmModelSetType(
+    char *name                          ///< Add comment.
+);
+
+/**
+ * 
+ *  Each of the function types above has a corresponding function which returns
+ *  the function given the model type:
+ * 
+ */
+
+/**
+ * 
+ * pmModelFunc is the function used to determine the value of the model at a
+ * specific coordinate, and is the one used by psMinimizeLMChi2.
+ * 
+ */
+pmModelFunc          pmModelFunc_GetFunction (pmModelType type);
+
+
+/**
+ * 
+ * pmModelFlux returns the total integrated flux for the given input parameters.
+ * 
+ */
+pmModelFlux          pmModelFlux_GetFunction (pmModelType type);
+
+
+/**
+ * 
+ * pmModelRadius returns the scaling radius at which the flux of the model
+ * matches the specified flux. This presumes that the model is a function of an
+ * elliptical contour.
+ * 
+ */
+pmModelRadius        pmModelRadius_GetFunction (pmModelType type);
+
+
+/**
+ * 
+ * pmModelLimits sets the parameter limit vectors for the function.
+ * 
+ */
+pmModelLimits        pmModelLimits_GetFunction (pmModelType type);
+
+
+/**
+ * 
+ * pmModelGuessFunc generates an initial guess for the model based on the
+ * provided source statistics (moments and pixel values as needed).
+ * 
+ */
+pmModelGuessFunc     pmModelGuessFunc_GetFunction (pmModelType type);
+
+
+/**
+ * 
+ * pmModelFromPSFFunc takes as input a representation of the psf and a value
+ * for the model and fills in the PSF parameters of the model. The input
+ * primarily relies upon the centroid coordinates of the input model, though the
+ * normalization may potentially be used.
+ * 
+ */
+pmModelFromPSFFunc   pmModelFromPSFFunc_GetFunction (pmModelType type);
+
+
+/**
+ * 
+ * pmModelFitStatusFunc returns a true or false values based on the success or
+ * failure of a model fit.  The success is determined by quantities such as the
+ * chisq or the signal-to-noise.
+ * 
+ */
+pmModelFitStatusFunc pmModelFitStatusFunc_GetFunction (pmModelType type);
+
+
+/** pmSourceModelGuess()
+ *
+ * Convert available data to an initial guess for the given model. This
+ * function allocates a pmModel entry for the pmSource structure based on the
+ * provided model selection. The method of defining the model parameter guesses
+ * are specified for each model below. The guess values are placed in the model
+ * parameters. The function returns TRUE on success or FALSE on failure.
+ *
+ */
+pmModel *pmSourceModelGuess(
+    pmSource *source,   ///< The input pmSource
+    pmModelType model   ///< The type of model to be created.
+);
+
+# endif /* PM_MODEL_GROUP_H */
Index: /tags/RHL_v1_1/psModules/src/objects/pmMoments.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmMoments.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmMoments.c	(revision 9533)
@@ -0,0 +1,44 @@
+/** @file  pmMoments.c
+ *
+ *  Functions defining the pmMoments structure
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-15 09:49:01 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include "pmMoments.h"
+
+/******************************************************************************
+pmMomentsAlloc(): Allocate the pmMoments structure and initialize the members
+to zero.
+*****************************************************************************/
+pmMoments *pmMomentsAlloc()
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    pmMoments *tmp = (pmMoments *) psAlloc(sizeof(pmMoments));
+    tmp->x = 0.0;
+    tmp->y = 0.0;
+    tmp->Sx = 0.0;
+    tmp->Sy = 0.0;
+    tmp->Sxy = 0.0;
+    tmp->Sum = 0.0;
+    tmp->Peak = 0.0;
+    tmp->Sky = 0.0;
+    tmp->nPixels = 0;
+
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(tmp);
+}
+
Index: /tags/RHL_v1_1/psModules/src/objects/pmMoments.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmMoments.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmMoments.h	(revision 9533)
@@ -0,0 +1,45 @@
+/** @file  pmMoments.h
+ *
+ *  Definitions of the moments structure
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:01:05 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+# ifndef PM_MOMENTS_H
+# define PM_MOMENTS_H
+
+/** pmMoments data structure
+ *
+ * One of the simplest measurements which can be made quickly for an object
+ * are the object moments. We specify a structure to carry the moment information
+ * for a specific source:
+ *
+ */
+typedef struct
+{
+    float x;     ///< X-coord of centroid.
+    float y;     ///< Y-coord of centroid.
+    float Sx;    ///< x-second moment.
+    float Sy;    ///< y-second moment.
+    float Sxy;   ///< xy cross moment.
+    float Sum;   ///< Pixel sum above sky (background).
+    float Peak;  ///< Peak counts above sky.
+    float Sky;   ///< Sky level (background).
+    float dSky;  ///< local Sky variance
+    float SN;    ///< approx signal-to-noise
+    int nPixels; ///< Number of pixels used.
+}
+pmMoments;
+
+/** pmMomentsAlloc()
+ *
+ */
+pmMoments *pmMomentsAlloc();
+
+# endif
Index: /tags/RHL_v1_1/psModules/src/objects/pmObjects.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmObjects.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmObjects.c	(revision 9533)
@@ -0,0 +1,25 @@
+/** @file  pmObjects.c
+ *
+ *  This file will ...
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA: significant modifications.
+ *
+ *  @version $Revision: 1.13 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-15 09:49:01 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmObjects.h"
+#include "pmModelGroup.h"
+
Index: /tags/RHL_v1_1/psModules/src/objects/pmObjects.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmObjects.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmObjects.h	(revision 9533)
@@ -0,0 +1,71 @@
+/** @file  pmObjects.h
+ *
+ * The process of finding, measuring, and classifying astronomical sources on
+ * images is one of the critical tasks of the IPP or any astronomical software
+ * system. This file will define structures and functions related to the task
+ * of source detection and measurement. The elements defined in this section
+ * are generally low-level components which can be connected together to
+ * construct a complete object measurement suite.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:01:05 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#if !defined(PM_OBJECTS_H)
+#define PM_OBJECTS_H
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include "pslib.h"
+
+/**
+ *
+ * This function converts the source classification into the closest available
+ * approximation to the Dophot classification scheme:
+ * XXX EAM : fix this to use current source classification scheme
+ *
+ * PM_SOURCE_DEFECT: 8
+ * PM_SOURCE_SATURATED: 8
+ * PM_SOURCE_SATSTAR: 10
+ * PM_SOURCE_PSFSTAR: 1
+ * PM_SOURCE_GOODSTAR: 1
+ * PM_SOURCE_POOR_FIT_PSF: 7
+ * PM_SOURCE_FAIL_FIT_PSF: 4
+ * PM_SOURCE_FAINTSTAR: 4
+ * PM_SOURCE_GALAXY: 2
+ * PM_SOURCE_FAINT_GALAXY: 2
+ * PM_SOURCE_DROP_GALAXY: 2
+ * PM_SOURCE_FAIL_FIT_GAL: 2
+ * PM_SOURCE_POOR_FIT_GAL: 2
+ * PM_SOURCE_OTHER: ?
+ *
+ */
+int pmSourceDophotType(
+    pmSource *source                    ///< Add comment.
+);
+
+
+/** pmSourceSextractType()
+ *
+ * This function converts the source classification into the closest available
+ * approximation to the Sextractor classification scheme. the correspondence is
+ * not yet defined (TBD) .
+ *
+ * XXX: Must code this.
+ *
+ */
+int pmSourceSextractType(
+    pmSource *source                    ///< Add comment.
+);
+
+
+#endif
Index: /tags/RHL_v1_1/psModules/src/objects/pmPSF.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmPSF.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmPSF.c	(revision 9533)
@@ -0,0 +1,321 @@
+/** @file  pmPSF.c
+ *
+ * This file contains typedefs for the Point-Spread Function and prototypes
+ * for functions that calculate the PSF.
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-15 09:49:01 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/*****************************************************************************/
+/* INCLUDE FILES                                                             */
+/*****************************************************************************/
+
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmGrowthCurve.h"
+#include "pmPSF.h"
+#include "pmModelGroup.h"
+
+/*****************************************************************************/
+/* DEFINE STATEMENTS                                                         */
+/*****************************************************************************/
+
+/*****************************************************************************/
+/* TYPE DEFINITIONS                                                          */
+/*****************************************************************************/
+
+/*****************************************************************************/
+/* GLOBAL VARIABLES                                                          */
+/*****************************************************************************/
+
+/*****************************************************************************/
+/* FILE STATIC VARIABLES                                                     */
+/*****************************************************************************/
+
+/*****************************************************************************/
+/* FUNCTION IMPLEMENTATION - LOCAL                                           */
+/*****************************************************************************/
+
+/*****************************************************************************/
+/* FUNCTION IMPLEMENTATION - PUBLIC                                          */
+/*****************************************************************************/
+
+
+
+/*****************************************************************************
+pmPSFFree(psf): function to free a pmPSF structure
+ *****************************************************************************/
+static void pmPSFFree (pmPSF *psf)
+{
+
+    if (psf == NULL)
+        return;
+
+    psFree (psf->ChiTrend);
+    psFree (psf->ApTrend);
+    psFree (psf->growth);
+    psFree (psf->params);
+    return;
+}
+
+
+
+/*****************************************************************************
+pmPSFAlloc (type): allocate a pmPSF.
+    NOTE: a PSF always has 4 parameters fewer than the equivalent model.
+      This is because those 4 parameters are
+ X-center
+ Y-center
+ Sky background value
+ Object Normalization
+ *****************************************************************************/
+pmPSF *pmPSFAlloc (pmModelType type, bool poissonErrors)
+{
+    int Nparams;
+
+    pmPSF *psf = (pmPSF *) psAlloc(sizeof(pmPSF));
+
+    psf->type     = type;
+    psf->chisq    = 0.0;
+    psf->ApResid  = 0.0;
+    psf->dApResid = 0.0;
+    psf->skyBias  = 0.0;
+    psf->skySat   = 0.0;
+    psf->poissonErrors = poissonErrors;
+
+    // the ApTrend components are (x, y, r2rflux, flux)
+    psf->ApTrend = psPolynomial4DAlloc (PS_POLYNOMIAL_ORD, 2, 2, 1, 1);
+    pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_SKYBIAS);
+
+    if (psf->poissonErrors) {
+        psf->ChiTrend = psPolynomial1DAlloc (PS_POLYNOMIAL_ORD, 1);
+    } else {
+        psf->ChiTrend = psPolynomial1DAlloc (PS_POLYNOMIAL_ORD, 2);
+    }
+
+    // don't define a growth curve : user needs to choose radius bins
+    psf->growth = NULL;
+
+    Nparams = pmModelParameterCount (type);
+    if (!Nparams) {
+        psError(PS_ERR_UNKNOWN, true, "Undefined pmModelType");
+        return(NULL);
+    }
+
+    psf->params = psArrayAlloc(Nparams - 4);
+    psf->params->n = psf->params->nalloc;
+    for (int i = 0; i < psf->params->n; i++) {
+        // XXX EAM : make this a user-defined value?
+        psf->params->data[i] = psPolynomial2DAlloc(PS_POLYNOMIAL_ORD, 1, 1);
+    }
+
+    psMemSetDeallocator(psf, (psFreeFunc) pmPSFFree);
+    return(psf);
+}
+
+
+
+/*****************************************************************************
+pmPSFFromModels (*psf, *models, *mask): build a PSF from a collection of
+free-fitted models.  The PSF ignores the first 4 (independent) model
+parameters and constructs a polynomial fit to the remaining as a function of
+image coordinate.
+    input: an array of pmModels, pre-allocated psf
+Note: some of the array entries may be NULL (failed fits); ignore them.
+ *****************************************************************************/
+bool pmPSFFromModels (pmPSF *psf, psArray *models, psVector *mask)
+{
+
+    // construct the fit vectors from the collection of objects
+    // use the mask to ignore missing fits
+    psVector *x  = psVectorAlloc (models->n, PS_TYPE_F64);
+    psVector *y  = psVectorAlloc (models->n, PS_TYPE_F64);
+    psVector *z  = psVectorAlloc (models->n, PS_TYPE_F64);
+    psVector *dz = psVectorAlloc (models->n, PS_TYPE_F64);
+    x->n = x->nalloc;
+    y->n = y->nalloc;
+    z->n = z->nalloc;
+    dz->n = dz->nalloc;
+
+    for (int i = 0; i < models->n; i++) {
+        pmModel *model = models->data[i];
+        if (model == NULL)
+            continue;
+
+        // XXX EAM : this is fragile: x and y must be stored consistently at 2,3
+        // note that the data is provided in the F64 array
+        x->data.F64[i] = model->params->data.F32[2];
+        y->data.F64[i] = model->params->data.F32[3];
+    }
+
+    // we are doing a robust fit.  after each pass, we drop points which are
+    // more deviant than three sigma.
+    // mask is currently updated for each pass. this is inconsistent?
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+
+    for (int i = 0; i < psf->params->n; i++) {
+        for (int j = 0; j < models->n; j++) {
+            pmModel *model = models->data[j];
+            if (model == NULL)
+                continue;
+            z->data.F64[j] = model->params->data.F32[i + 4];
+            dz->data.F64[j] = 1;
+            // XXX EAM : need to use actual errors?
+            // XXX EAM : this is fragile: psf(Nparams) = model(Nparams) - 4
+        }
+
+        // XXX EAM : this is the expected API (cycle 7? cycle 8?)
+        psf->params->data[i] = psVectorClipFitPolynomial2D(psf->params->data[i], stats, mask, 0xff, z, dz, x, y);
+
+        // XXX EAM : drop this when above is implemented...
+        // psf->params->data[i] = RobustFit2D (psf->params->data[i], mask, x, y, z, dz);
+
+        // psTrace ("psphot.psftest", 3, "keeping %d of %d PSF candidates (PSF param %d)\n", Nkeep, mask->n, i);
+        // psPolynomial2DDump (psf->params->data[i]);
+    }
+
+    psFree (stats);
+    psFree (x);
+    psFree (y);
+    psFree (z);
+    psFree (dz);
+    return (true);
+}
+
+
+
+/*****************************************************************************
+pmModelFromPSF (*modelEXT, *psf):  use the model position parameters to
+construct a realization of the PSF model at the object coordinates
+ *****************************************************************************/
+pmModel *pmModelFromPSF (pmModel *modelEXT, pmPSF *psf)
+{
+
+    // need to define the relationship between the modelEXT and modelPSF ?
+
+    // find function used to set the model parameters
+    pmModelFromPSFFunc modelFromPSFFunc = pmModelFromPSFFunc_GetFunction (psf->type);
+
+    // allocate a new pmModel to hold the PSF version
+    pmModel *modelPSF = pmModelAlloc (psf->type);
+
+    // set model parameters for this source based on PSF information
+    modelFromPSFFunc (modelPSF, modelEXT, psf);
+
+    return (modelPSF);
+}
+
+// zero and mask out all terms:
+static bool maskAllTerms (psPolynomial4D *trend)
+{
+
+    for (int i = 0; i < trend->nX + 1; i++) {
+        for (int j = 0; j < trend->nY + 1; j++) {
+            for (int k = 0; k < trend->nZ + 1; k++) {
+                for (int m = 0; m < trend->nT + 1; m++) {
+                    trend->mask[i][j][k][m] = 1;  // mask coeff
+                    trend->coeff[i][j][k][m] = 0;  // zero coeff
+                }
+            }
+        }
+    }
+    return true;
+}
+
+/***********************************************
+ * this function masks the psf.ApTrend polynomial 
+ * to enable the specific subset of the coefficients
+ **********************************************/
+bool pmPSF_MaskApTrend (psPolynomial4D *trend,
+                        pmPSF_ApTrendOptions option)
+{
+
+    switch (option) {
+    case PM_PSF_NONE:
+        maskAllTerms (trend);
+        return true;
+
+    case PM_PSF_CONSTANT:
+        maskAllTerms (trend);
+        trend->mask[0][0][0][0] = 0;  // unmask constant
+        return true;
+
+    case PM_PSF_SKYBIAS:
+        maskAllTerms (trend);
+        trend->mask[0][0][0][0] = 0;  // unmask constant
+        trend->mask[0][0][1][0] = 0;  // unmask skybias
+        return true;
+
+    case PM_PSF_SKYSAT:
+        maskAllTerms (trend);
+        trend->mask[0][0][0][0] = 0;  // unmask constant
+        trend->mask[0][0][1][0] = 0;  // unmask skybias
+        trend->mask[0][0][0][1] = 0;  // unmask skysat
+        return true;
+
+    case PM_PSF_XY_LIN:
+        maskAllTerms (trend);
+        trend->mask[0][0][0][0] = 0;  // unmask constant
+        trend->mask[1][0][0][0] = 0;  // unmask x
+        trend->mask[0][1][0][0] = 0;  // unmask y
+        return true;
+
+    case PM_PSF_XY_QUAD:
+        maskAllTerms (trend);
+        trend->mask[0][0][0][0] = 0;  // unmask constant
+        trend->mask[1][0][0][0] = 0;  // unmask x
+        trend->mask[2][0][0][0] = 0;  // unmask x^2
+        trend->mask[1][1][0][0] = 0;  // unmask x y
+        trend->mask[0][1][0][0] = 0;  // unmask y
+        trend->mask[0][2][0][0] = 0;  // unmask y^2
+        return true;
+
+    case PM_PSF_SKY_XY_LIN:
+        maskAllTerms (trend);
+        trend->mask[0][0][0][0] = 0;  // unmask constant
+        trend->mask[1][0][0][0] = 0;  // unmask x
+        trend->mask[0][1][0][0] = 0;  // unmask y
+        trend->mask[0][0][1][0] = 0;  // unmask skybias
+        return true;
+
+    case PM_PSF_SKYSAT_XY_LIN:
+        maskAllTerms (trend);
+        trend->mask[0][0][0][0] = 0;  // unmask constant
+        trend->mask[1][0][0][0] = 0;  // unmask x
+        trend->mask[0][1][0][0] = 0;  // unmask y
+        trend->mask[0][0][1][0] = 0;  // unmask skybias
+        trend->mask[0][0][0][1] = 0;  // unmask skysat
+        return true;
+
+    case PM_PSF_ALL:
+    default:
+        maskAllTerms (trend);
+        trend->mask[0][0][0][0] = 0;  // unmask constant
+        trend->mask[0][0][1][0] = 0;  // unmask skybias
+        trend->mask[0][0][0][1] = 0;  // unmask skysat
+
+        trend->mask[1][0][0][0] = 0;  // unmask x
+        trend->mask[2][0][0][0] = 0;  // unmask x^2
+        trend->mask[1][1][0][0] = 0;  // unmask x y
+        trend->mask[0][1][0][0] = 0;  // unmask y
+        trend->mask[0][2][0][0] = 0;  // unmask y^2
+        return true;
+    }
+    return false;
+}
Index: /tags/RHL_v1_1/psModules/src/objects/pmPSF.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmPSF.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmPSF.h	(revision 9533)
@@ -0,0 +1,105 @@
+/** @file  pmPSF.h
+ *
+ * This file contains typedefs for the Point-Spread Function and prototypes
+ * for functions that calculate the PSF.
+ *
+ *  @author EAM, IfA
+ *
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+# ifndef PM_PSF_H
+# define PM_PSF_H
+
+/** pmPSF data structure
+ * 
+ * It is useful to generate a model to define the point-spread-function which
+ * describes the flux distribution for unresolved sources in an image. In
+ * general, the PSF varies with position in the image. We allow any of the source
+ * models defined for the pmModel to represent the PSF. For a given source model,
+ * the 2D spatial variation of all of the source parameters, except the first
+ * four PSF-independent parameters, are represented as polynomial, stored in a
+ * psArray. The other elements of the structure define the quality of the PSF
+ * determination.
+ * 
+ */
+typedef struct
+{
+    pmModelType type;   ///< PSF Model in use
+    psArray *params;   ///< Model parameters (psPolynomial2D)
+    psPolynomial1D *ChiTrend;  ///< Chisq vs flux fit (correction for systematic errors)
+    psPolynomial4D *ApTrend;  ///< ApResid vs (x,y,rflux) (rflux = ten(0.4*mInst))
+    //pmGrowthCurve *growth;  ///< apMag vs Radius // move to end as gdb is confused by it here
+    float ApResid;   ///< apMag - psfMag (for PSF stars)
+    float dApResid;   ///< scatter of ApResid
+    float skyBias;   ///< implied residual sky offset from ApResid fit
+    float skySat;   ///< roll-over of ApResid fit
+    float chisq;   ///< PSF goodness statistic (unused??)
+    int nPSFstars;   ///< number of stars used to measure PSF
+    int nApResid;   ///< number of stars used to measure ApResid
+    bool poissonErrors;
+    pmGrowthCurve *growth;  ///< apMag vs Radius
+}
+pmPSF;
+
+typedef enum {
+    PM_PSF_NONE,
+    PM_PSF_CONSTANT,
+    PM_PSF_SKYBIAS,
+    PM_PSF_SKYSAT,
+    PM_PSF_XY_LIN,
+    PM_PSF_XY_QUAD,
+    PM_PSF_SKY_XY_LIN,
+    PM_PSF_SKYSAT_XY_LIN,
+    PM_PSF_ALL
+} pmPSF_ApTrendOptions;
+
+/**
+ * 
+ * Allocator for the pmPSF structure.
+ * 
+ */
+pmPSF *pmPSFAlloc(
+    pmModelType type,   // type of model for PSF
+    bool poissonErrors   ///< use poissonian errors or not?
+);
+
+
+/**
+ * 
+ * This function takes a collection of pmModel fitted models from across a
+ * single image and builds a pmPSF representation of the PSF. The input array of
+ * model fits may consist of entries to be ignored (noted by a non-zero mask
+ * entry). The analysis of the models fits a 2D polynomial for each parameter to
+ * the collection of model parameters as a function of position (and
+ * normalization?). In this process, some of the input models may be marked as
+ * outliers and excluded from the fit. These elements will be marked with a
+ * specific mask value (1 == PSFTRY_MASK_OUTLIER).
+ * 
+ */
+bool pmPSFFromModels(
+    pmPSF *psf,                         ///< Add comment
+    psArray *models,                    ///< Add comment
+    psVector *mask                      ///< Add comment
+);
+
+
+/**
+ * 
+ * This function constructs a pmModel instance based on the pmPSF description
+ * of the PSF. The input is a pmModel with at least the values of the centroid
+ * coordinates (possibly normalization if this is needed) defined. The values of
+ * the PSF-dependent parameters are specified for the specific realization based
+ * on the coordinates of the object.
+ * 
+ */
+pmModel *pmModelFromPSF(
+    pmModel *model,                     ///< Add comment
+    pmPSF *psf                          ///< Add comment
+);
+
+bool pmPSF_MaskApTrend (psPolynomial4D *trend, pmPSF_ApTrendOptions option);
+
+# endif
Index: /tags/RHL_v1_1/psModules/src/objects/pmPSF_IO.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmPSF_IO.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmPSF_IO.c	(revision 9533)
@@ -0,0 +1,316 @@
+/** @file  pmPSF_IO.c
+ *
+ * This file contains functions to read and write PSF models using the psMetadata Config file
+ * format.
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-15 09:49:01 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+/*****************************************************************************/
+/* INCLUDE FILES                                                             */
+/*****************************************************************************/
+
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAfile.h"
+#include "pmFPAfile.h"
+
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmModel.h"
+#include "pmPSF.h"
+#include "pmPSF_IO.h"
+#include "pmSource.h"
+#include "pmModelGroup.h"
+#include "pmSourceIO.h"
+
+psMetadata *pmPSFtoMD (psMetadata *metadata, pmPSF *psf)
+{
+
+    if (metadata == NULL) {
+        metadata = psMetadataAlloc ();
+    }
+
+    char *modelName = pmModelGetType (psf->type);
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_MODEL_NAME", PS_DATA_STRING, "PSF model name", modelName);
+
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_POISSON_ERRORS", PS_DATA_BOOL, "Poisson errors for fits", psf->poissonErrors);
+
+    int nPar = pmModelParameterCount (psf->type)    ;
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_MODEL_NPAR", PS_DATA_S32, "PSF model parameter count", nPar);
+
+    for (int i = 0; i < nPar - 4; i++) {
+        psPolynomial2D *poly = psf->params->data[i];
+        psPolynomial2DtoMD (metadata, poly, "PSF_PAR%02d", i);
+    }
+    psPolynomial4DtoMD (metadata, psf->ApTrend, "APTREND");
+
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_AP_RESID", PS_DATA_F32, "aperture residual", psf->ApResid);
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_dAP_RESID", PS_DATA_F32, "aperture residual scatter", psf->dApResid);
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_SKY_BIAS", PS_DATA_F32, "sky bias level", psf->skyBias);
+
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_CHISQ", PS_DATA_F32, "chi-square for fit", psf->chisq);
+    psMetadataAdd (metadata, PS_LIST_TAIL, "PSF_NSTARS", PS_DATA_S32, "number of stars used to measure PSF", psf->nPSFstars);
+
+    return metadata;
+}
+
+pmPSF *pmPSFfromMD (psMetadata *metadata)
+{
+
+    bool status;
+    char keyword[80];
+
+    char *modelName = psMetadataLookupPtr (&status, metadata, "PSF_MODEL_NAME");
+    pmModelType type = pmModelSetType (modelName);
+
+    bool poissonErrors = psMetadataLookupPtr (&status, metadata, "PSF_POISSON_ERRORS");
+    if (!status)
+        poissonErrors = true;
+
+    pmPSF *psf = pmPSFAlloc (type, poissonErrors);
+
+    int nPar = psMetadataLookupS32 (&status, metadata, "PSF_MODEL_NPAR");
+    if (nPar != pmModelParameterCount (psf->type))
+        psAbort ("read PSF" , "mismatch model par count");
+
+    for (int i = 0; i < nPar - 4; i++) {
+        sprintf (keyword, "PSF_PAR%02d", i);
+        psMetadata *folder = psMetadataLookupPtr (&status, metadata, keyword);
+        psPolynomial2D *poly = psPolynomial2DfromMD (folder);
+        psFree (psf->params->data[i]);
+        psf->params->data[i] = poly;
+    }
+    sprintf (keyword, "APTREND");
+    psMetadata *folder = psMetadataLookupPtr (&status, metadata, keyword);
+    psPolynomial4D *poly = psPolynomial4DfromMD (folder);
+    psFree (psf->ApTrend);
+    psf->ApTrend = poly;
+
+    psf->ApResid = psMetadataLookupF32 (&status, metadata, "PSF_AP_RESID");
+    psf->dApResid = psMetadataLookupF32 (&status, metadata, "PSF_dAP_RESID");
+    psf->skyBias = psMetadataLookupF32 (&status, metadata, "PSF_SKY_BIAS");
+
+    psf->chisq = psMetadataLookupF32 (&status, metadata, "PSF_CHISQ");
+    psf->nPSFstars = psMetadataLookupS32 (&status, metadata, "PSF_NSTARS");
+
+    psFree (metadata);
+    return (psf);
+}
+
+bool pmFPAviewWritePSFmodel (const pmFPAview *view, pmFPAfile *file)
+{
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        pmFPAWritePSFmodel (fpa, view, file);
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        pmChipWritePSFmodel (chip, view, file);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        pmCellWritePSFmodel (cell, view, file);
+        return true;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    pmReadoutWritePSFmodel (readout, view, file);
+    return true;
+}
+
+// read in all chip-level PSFmodel files for this FPA
+bool pmFPAWritePSFmodel (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file)
+{
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+
+        pmChip *chip = fpa->chips->data[i];
+        pmChipWritePSFmodel (chip, view, file);
+    }
+    return true;
+}
+
+// read in all cell-level PSFmodel files for this chip
+bool pmChipWritePSFmodel (pmChip *chip, const pmFPAview *view, pmFPAfile *file)
+{
+
+    for (int i = 0; i < chip->cells->n; i++) {
+
+        pmCell *cell = chip->cells->data[i];
+        pmCellWritePSFmodel (cell, view, file);
+    }
+    return true;
+}
+
+// read in all readout-level PSFmodel files for this cell
+bool pmCellWritePSFmodel (pmCell *cell, const pmFPAview *view, pmFPAfile *file)
+{
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+
+        pmReadout *readout = cell->readouts->data[i];
+        pmReadoutWritePSFmodel (readout, view, file);
+    }
+    return true;
+}
+
+// read in all readout-level Objects files for this cell
+bool pmReadoutWritePSFmodel (pmReadout *readout, const pmFPAview *view, pmFPAfile *file)
+{
+    bool status;
+    char *filename;
+
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+
+    switch (file->type) {
+    case PM_FPA_FILE_PSF:
+        filename = pmFPAfileNameFromRule (file->filerule, file, view);
+        psMetadata *psfData = pmPSFtoMD (NULL, psf);
+        psMetadataConfigWrite (psfData, filename);
+        psFree (psfData);
+        psFree (filename);
+        return true;
+
+    default:
+        fprintf (stderr, "warning: type mismatch\n");
+        break;
+    }
+    return false;
+}
+
+
+
+bool pmFPAviewReadPSFmodel (const pmFPAview *view, pmFPAfile *file)
+{
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        pmFPAReadPSFmodel (fpa, view, file);
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        pmChipReadPSFmodel (chip, view, file);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        pmCellReadPSFmodel (cell, view, file);
+        return true;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    pmReadoutReadPSFmodel (readout, view, file);
+    return true;
+}
+
+// read in all chip-level PSFmodel files for this FPA
+bool pmFPAReadPSFmodel (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file)
+{
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+
+        pmChip *chip = fpa->chips->data[i];
+        pmChipReadPSFmodel (chip, view, file);
+    }
+    return true;
+}
+
+// read in all cell-level PSFmodel files for this chip
+bool pmChipReadPSFmodel (pmChip *chip, const pmFPAview *view, pmFPAfile *file)
+{
+
+    for (int i = 0; i < chip->cells->n; i++) {
+
+        pmCell *cell = chip->cells->data[i];
+        pmCellReadPSFmodel (cell, view, file);
+    }
+    return true;
+}
+
+// read in all readout-level PSFmodel files for this cell
+bool pmCellReadPSFmodel (pmCell *cell, const pmFPAview *view, pmFPAfile *file)
+{
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+
+        pmReadout *readout = cell->readouts->data[i];
+        pmReadoutReadPSFmodel (readout, view, file);
+    }
+    return true;
+}
+
+// read in all readout-level Objects files for this cell
+bool pmReadoutReadPSFmodel (pmReadout *readout, const pmFPAview *view, pmFPAfile *file)
+{
+
+    unsigned int Nfail;
+    char *filename;
+
+    switch (file->type) {
+    case PM_FPA_FILE_PSF:
+        filename = pmFPAfileNameFromRule (file->filerule, file, view);
+
+        psMetadata *psfData = psMetadataConfigRead(NULL, &Nfail, filename, FALSE);
+        pmPSF *psf = pmPSFfromMD (psfData);
+        psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.PSF", PS_DATA_UNKNOWN, "psphot psf", psf);
+
+        psFree (psf);
+        psFree (psfData);
+        psFree (filename);
+
+        return true;
+
+    default:
+        fprintf (stderr, "warning: type mismatch\n");
+        break;
+    }
+    return false;
+}
+
Index: /tags/RHL_v1_1/psModules/src/objects/pmPSF_IO.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmPSF_IO.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmPSF_IO.h	(revision 9533)
@@ -0,0 +1,32 @@
+/** @file  pmPSF.h
+ *
+ * This file contains typedefs for the Point-Spread Function and prototypes
+ * for functions that calculate the PSF.
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:01:05 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+# ifndef PM_PSF_IO_H
+# define PM_PSF_IO_H
+
+psMetadata *pmPSFtoMD (psMetadata *metadata, pmPSF *psf);
+pmPSF *pmPSFfromMD (psMetadata *metadata);
+bool pmFPAviewWritePSFmodel (const pmFPAview *view, pmFPAfile *file);
+bool pmFPAWritePSFmodel (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file);
+bool pmChipWritePSFmodel (pmChip *chip, const pmFPAview *view, pmFPAfile *file);
+bool pmCellWritePSFmodel (pmCell *cell, const pmFPAview *view, pmFPAfile *file);
+bool pmReadoutWritePSFmodel (pmReadout *readout, const pmFPAview *view, pmFPAfile *file);
+
+bool pmFPAviewReadPSFmodel (const pmFPAview *view, pmFPAfile *file);
+bool pmFPAReadPSFmodel (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file);
+bool pmChipReadPSFmodel (pmChip *chip, const pmFPAview *view, pmFPAfile *file);
+bool pmCellReadPSFmodel (pmCell *cell, const pmFPAview *view, pmFPAfile *file);
+bool pmReadoutReadPSFmodel (pmReadout *readout, const pmFPAview *view, pmFPAfile *file);
+
+# endif
Index: /tags/RHL_v1_1/psModules/src/objects/pmPSFtry.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmPSFtry.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmPSFtry.c	(revision 9533)
@@ -0,0 +1,302 @@
+/** @file  pmPSFtry.c
+ *
+ *  XXX: need description of file purpose
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.18 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-10-13 02:29:14 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmGrowthCurve.h"
+#include "pmPSF.h"
+#include "pmPSFtry.h"
+#include "pmModelGroup.h"
+#include "pmSourceFitModel.h"
+#include "pmSourcePhotometry.h"
+
+// ********  pmPSFtry functions  **************************************************
+// * pmPSFtry holds a single pmPSF model test, with the input sources, the freely
+// * fitted version of the model, the pmPSF fit to the fitted model parameters,
+// * and the PSF fits to the source. It also includes the statistics from the
+// * fits, both the individual sources, and the collection
+
+// free a pmPSFtry structure
+static void pmPSFtryFree (pmPSFtry *test)
+{
+
+    if (test == NULL)
+        return;
+
+    psFree (test->psf);
+    psFree (test->sources);
+    psFree (test->modelEXT);
+    psFree (test->modelPSF);
+    psFree (test->metric);
+    psFree (test->fitMag);
+    psFree (test->mask);
+    return;
+}
+
+// allocate a pmPSFtry based on the desired sources and the model (identified by name)
+pmPSFtry *pmPSFtryAlloc (psArray *sources, char *modelName, bool poissonErrors)
+{
+
+    pmModelType type;
+
+    pmPSFtry *test = (pmPSFtry *) psAlloc(sizeof(pmPSFtry));
+
+    // XXX probably need to increment ref counter
+    type           = pmModelSetType (modelName);
+    test->psf      = pmPSFAlloc (type, poissonErrors);
+    test->sources  = psMemIncrRefCounter(sources);
+    test->modelEXT = psArrayAlloc (sources->n);
+    test->modelPSF = psArrayAlloc (sources->n);
+    test->metric   = psVectorAlloc (sources->n, PS_TYPE_F64);
+    test->fitMag   = psVectorAlloc (sources->n, PS_TYPE_F64);
+    test->mask     = psVectorAlloc (sources->n, PS_TYPE_U8);
+    test->metric->n = test->fitMag->n = test->mask->n = sources->n;
+    test->modelEXT->n = test->modelPSF->n = sources->n;
+
+    for (int i = 0; i < test->modelEXT->n; i++) {
+        test->mask->data.U8[i]  = 0;
+        test->modelEXT->data[i] = NULL;
+        test->modelPSF->data[i] = NULL;
+        test->metric->data.F64[i] = 0;
+        test->fitMag->data.F64[i] = 0;
+    }
+
+    psMemSetDeallocator(test, (psFreeFunc) pmPSFtryFree);
+    return (test);
+}
+
+// build a pmPSFtry for the given model:
+// - fit each source with the free-floating model
+// - construct the pmPSF from the collection of models
+// - fit each source with the PSF-parameter models
+// - measure the pmPSF quality metric (dApResid)
+
+// sources used in for pmPSFtry may be masked by the analysis
+// mask values indicate the reason the source was rejected:
+
+pmPSFtry *pmPSFtryModel (psArray *sources, char *modelName, float RADIUS, bool poissonErrors)
+{
+    bool status;
+    float obsMag;
+    float fitMag;
+    float x;
+    float y;
+    int Next = 0;
+    int Npsf = 0;
+
+    pmPSFtry *psfTry = pmPSFtryAlloc (sources, modelName, poissonErrors);
+
+    // stage 1:  fit an independent model (freeModel) to all sources
+    psTimerStart ("fit");
+    for (int i = 0; i < psfTry->sources->n; i++) {
+
+        pmSource *source = psfTry->sources->data[i];
+        pmModel  *model  = pmSourceModelGuess (source, psfTry->psf->type);
+        x = source->peak->x;
+        y = source->peak->y;
+
+        // set temporary object mask and fit object
+        // fit model as EXT, not PSF
+
+        psImageKeepCircle (source->mask, x, y, RADIUS, "OR", PM_MASK_MARK);
+        status = pmSourceFitModel (source, model, PM_SOURCE_FIT_EXT);
+        psImageKeepCircle (source->mask, x, y, RADIUS, "AND", NOT_U8(PM_MASK_MARK));
+
+        // exclude the poor fits
+        if (!status) {
+            psfTry->mask->data.U8[i] = PSFTRY_MASK_EXT_FAIL;
+            psFree (model);
+            continue;
+        }
+        psfTry->modelEXT->data[i] = model;
+        Next ++;
+    }
+    psLogMsg ("psphot.psftry", 4, "fit ext:   %f sec for %d of %ld sources\n", psTimerMark ("fit"), Next, sources->n);
+    psTrace ("psphot.psftry", 3, "keeping %d of %ld PSF candidates (EXT)\n", Next, sources->n);
+
+    // stage 2: construct a psf (pmPSF) from this collection of model fits
+    pmPSFFromModels (psfTry->psf, psfTry->modelEXT, psfTry->mask);
+
+    // stage 3: refit with fixed shape parameters
+    psTimerStart ("fit");
+    for (int i = 0; i < psfTry->sources->n; i++) {
+        // masked for: bad model fit, outlier in parameters
+        if (psfTry->mask->data.U8[i] & PSFTRY_MASK_ALL)
+            continue;
+
+        pmSource *source = psfTry->sources->data[i];
+        pmModel  *modelEXT = psfTry->modelEXT->data[i];
+
+        // set shape for this model based on PSF
+        pmModel *modelPSF = pmModelFromPSF (modelEXT, psfTry->psf);
+        x = source->peak->x;
+        y = source->peak->y;
+
+        psImageKeepCircle (source->mask, x, y, RADIUS, "OR", PM_MASK_MARK);
+        status = pmSourceFitModel (source, modelPSF, PM_SOURCE_FIT_PSF);
+
+        // skip poor fits
+        if (!status) {
+            psfTry->mask->data.U8[i] = PSFTRY_MASK_PSF_FAIL;
+            psFree (modelPSF);
+            goto next_source;
+        }
+
+        // otherwise, save the resulting model
+        psfTry->modelPSF->data[i] = modelPSF;
+
+        // XXX : use a different aperture radius from the fit radius?
+        // XXX : use a different estimator for the local sky?
+        // XXX : pass 'source' as input?
+        if (!pmSourcePhotometryModel (&fitMag, modelPSF)) {
+            psfTry->mask->data.U8[i] = PSFTRY_MASK_BAD_PHOT;
+            goto next_source;
+        }
+        if (!pmSourcePhotometryAper (&obsMag, modelPSF, source->pixels, source->mask)) {
+            psfTry->mask->data.U8[i] = PSFTRY_MASK_BAD_PHOT;
+            goto next_source;
+        }
+
+        psfTry->metric->data.F64[i] = obsMag - fitMag;
+        psfTry->fitMag->data.F64[i] = fitMag;
+        Npsf ++;
+
+next_source:
+        psImageKeepCircle (source->mask, x, y, RADIUS, "AND", NOT_U8(PM_MASK_MARK));
+
+    }
+    psfTry->psf->nPSFstars = Npsf;
+
+    psLogMsg ("psphot.psftry", 4, "fit psf:   %f sec for %d of %ld sources\n", psTimerMark ("fit"), Npsf, sources->n);
+    psTrace ("psphot.psftry", 3, "keeping %d of %ld PSF candidates (PSF)\n", Npsf, sources->n);
+
+    // measure the chi-square trend as a function of flux (PAR[PM_PAR_I0])
+    // this should be linear for Poisson errors and quadratic for constant sky errors
+    psVector *flux  = psVectorAlloc (psfTry->sources->n, PS_TYPE_F64);
+    psVector *chisq = psVectorAlloc (psfTry->sources->n, PS_TYPE_F64);
+    psVector *mask  = psVectorAlloc (psfTry->sources->n, PS_TYPE_MASK);
+    flux->n = mask->n = chisq->n = psfTry->sources->n;
+
+    // write sources with models first
+    for (int i = 0; i < psfTry->sources->n; i++) {
+        pmModel *model = psfTry->modelPSF->data[i];
+        if (model == NULL) {
+            flux->data.F64[i] = 0.0;
+            chisq->data.F64[i] = 0.0;
+            mask->data.U8[i] = 1;
+        } else {
+            flux->data.F64[i] = model->params->data.F32[PM_PAR_I0];
+            chisq->data.F64[i] = model[0].chisq/model[0].nDOF;
+            mask->data.U8[i] = 0;
+        }
+    }
+    // use 3hi/3lo sigma clipping on the r2rflux vs metric fit
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    stats->clipSigma = 3.0;
+    stats->clipIter = 3;
+    // linear clipped fit of ApResid to r2rflux
+    psfTry->psf->ChiTrend =
+        psVectorClipFitPolynomial1D(psfTry->psf->ChiTrend, stats, mask, 1, chisq, NULL, flux);
+    psLogMsg ("pmPSFtry", 4, "chisq vs flux fit: %f +/- %f\n", stats->sampleMedian, stats->sampleStdev);
+
+    psFree (flux);
+    psFree (stats);
+    psFree (mask);
+    psFree (chisq);
+
+    if (psfTry->psf->ChiTrend == NULL) {
+        psError(PS_ERR_UNKNOWN, false, "Failed to fit psf->ChiTrend");
+        psFree(psfTry);
+        return NULL;
+    }
+
+    for (int i = 0; i < psfTry->psf->ChiTrend->nX + 1; i++) {
+        psLogMsg ("pmPSFtry", 4, "chisq vs flux fit term %d: %f +/- %f\n", i, psfTry->psf->ChiTrend->coeff[i]*pow(10000, i), psfTry->psf->ChiTrend->coeffErr[i]*pow(10000,i));
+    }
+
+    // XXX this function wants aperture radius for pmSourcePhotometry
+    pmPSFtryMetric (psfTry, RADIUS);
+    psLogMsg ("psphot.pspsf", 3, "try model %s, ap-fit: %f +/- %f : sky bias: %f\n",
+              modelName, psfTry->psf->ApResid, psfTry->psf->dApResid, psfTry->psf->skyBias);
+
+    return (psfTry);
+}
+
+bool pmPSFtryMetric (pmPSFtry *psfTry, float RADIUS)
+{
+    // the measured (aperture - fit) magnitudes (dA == psfTry->metric)
+    //   depend on both the true ap-fit (dAo) and the bias in the sky measurement:
+    //     dA = dAo + dsky/flux
+    //   where flux is the flux of the star
+    // we fit this trend to find the infinite flux aperture correction (dAo),
+    //   the nominal sky bias (dsky), and the error on dAo
+    // the values of dA are contaminated by stars with close neighbors in the aperture
+    //   we use an outlier rejection to avoid this bias
+
+    // r2rflux = radius^2 * ten(0.4*fitMag);
+    psVector *r2rflux = psVectorAlloc (psfTry->sources->n, PS_TYPE_F64);
+    r2rflux->n = psfTry->sources->n;
+
+    for (int i = 0; i < psfTry->sources->n; i++) {
+        if (psfTry->mask->data.U8[i] & PSFTRY_MASK_ALL)
+            continue;
+        r2rflux->data.F64[i] = PS_SQR(RADIUS) * pow(10.0, 0.4*psfTry->fitMag->data.F64[i]);
+    }
+
+    // use 3hi/1lo sigma clipping on the r2rflux vs metric fit
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    stats->min = 1.0;
+    stats->max = 3.0;
+    stats->clipIter = 3;
+
+    // fit ApTrend only to r2rflux, ignore x,y,flux variations for now
+    // linear clipped fit of ApResid to r2rflux
+    psPolynomial1D *poly = psPolynomial1DAlloc (PS_POLYNOMIAL_ORD, 1);
+    poly = psVectorClipFitPolynomial1D (poly, stats, psfTry->mask, PSFTRY_MASK_ALL, psfTry->metric, NULL, r2rflux);
+    if (poly == NULL) {
+        psError(PS_ERR_UNKNOWN, false, "Failed to fit clipped poly");
+        return false;
+    }
+    psLogMsg ("pmPSFtryMetric", 4, "fit stats: %f +/- %f\n", stats->sampleMedian, stats->sampleStdev);
+
+    pmPSF_MaskApTrend (psfTry->psf->ApTrend, PM_PSF_SKYBIAS);
+    psfTry->psf->ApTrend->coeff[0][0][0][0] = poly->coeff[0];
+    psfTry->psf->ApTrend->coeff[0][0][1][0] = 0;
+
+    psfTry->psf->skyBias  = poly->coeff[1];
+    psfTry->psf->ApResid  = poly->coeff[0];
+    psfTry->psf->dApResid = stats->sampleStdev;
+
+    psFree (r2rflux);
+    psFree (poly);
+    psFree (stats);
+
+    return true;
+}
+
+/*
+  (aprMag' - fitMag) = rflux*skyBias + ApTrend(x,y)
+  (aprMag - rflux*skyBias) - fitMag = ApTrend(x,y)
+  (aprMag - rflux*skyBias) = fitMag + ApTrend(x,y)
+*/
+
Index: /tags/RHL_v1_1/psModules/src/objects/pmPSFtry.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmPSFtry.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmPSFtry.h	(revision 9533)
@@ -0,0 +1,114 @@
+/** @file  pmPSFtry.h
+ *
+ * This file contains code that allows the user to try to fit several
+ * PSF models to an image.
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-06-03 01:02:08 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+# ifndef PM_PSF_TRY_H
+# define PM_PSF_TRY_H
+
+
+/**
+ * 
+ * This structure contains a pointer to the collection of sources which will
+ * be used to test the PSF model form. It lists the pmModelType type of model
+ * being tests, and contains an element to store the resulting psf
+ * representation. In addition, this structure carries the complete collection of
+ * EXT (floating parameter) and PSF (fixed parameter) model fits to each of the
+ * sources modelEXT and modelPSF. It also contains a mask which is set by the
+ * model fitting and psf fitting steps. For each model, the value of the quality
+ * metric is stored in the vector metric and the fitted instrumental magnitude is
+ * stored in fitMag. The quality metric for the PSF model is the aperture
+ * magnitude minus the fitted magnitude for each source. This collection of
+ * aperture residuals is examined in the analysis process, and a linear trend of
+ * the residual with the inverse object flux (ie, 100:4his structure contains a
+ * pointer to the collection of sources which will be used to test the PSF model
+ * form. It lists the pmModelType type of modmag) is fitted. The result of this
+ * fit is a measured sky bias (systematic error in the sky measured by the fits),
+ * an effective infinite-magnitude aperture correction (ApResid), and the scatter
+ * of the aperture correction for the ensemble of PSF stars (dApResid). The
+ * ultimate metric to intercompare multiple types of PSF models is the value of
+ * the aperture correction scatter.
+ * 
+ * XXX: There are many more members in the SDRS then in the prototype code.
+ * I stuck with the prototype code.
+ * 
+ * 
+ */
+typedef struct
+{
+    pmPSF      *psf;                    ///< Add comment.
+    psArray    *sources;                ///< pointers to the original sources
+    psArray    *modelEXT;               ///< model fits, floating parameters
+    psArray    *modelPSF;               ///< model fits, PSF parameters
+    psVector   *mask;                   ///< Add comment.
+    psVector   *metric;                 ///< Add comment.
+    psVector   *fitMag;                 ///< Add comment.
+}
+pmPSFtry;
+
+
+/** pmPSFtryMaskValues
+ * 
+ * The following datatype defines the masks used by the pmPSFtry analysis to
+ * identify sources which should or should not be included in the analysis.
+ * 
+ */
+typedef enum {
+    PSFTRY_MASK_CLEAR    = 0x00,        ///< Add comment.
+    PSFTRY_MASK_OUTLIER  = 0x01,        ///< 1: outlier in psf polynomial fit (provided by psPolynomials)
+    PSFTRY_MASK_EXT_FAIL = 0x02,        ///< 2: ext model failed to converge
+    PSFTRY_MASK_PSF_FAIL = 0x04,        ///< 3: psf model failed to converge
+    PSFTRY_MASK_BAD_PHOT = 0x08,        ///< 4: invalid source photometry
+    PSFTRY_MASK_ALL      = 0x0f,        ///< Add comment.
+} pmPSFtryMaskValues;
+
+
+/** pmPSFtryAlloc()
+ * 
+ * Allocate a pmPSFtry data structure.
+ * 
+ */
+pmPSFtry *pmPSFtryAlloc (psArray *sources, char *modelName, bool poissonErrors);
+
+/** pmPSFtryModel()
+ * 
+ * This function takes the input collection of sources and performs a complete
+ * analysis to determine a PSF model of the given type (specified by model name).
+ * The result is a pmPSFtry with the results of the analysis.
+ * 
+ */
+pmPSFtry *pmPSFtryModel (psArray *sources, char *modelName, float RADIUS, bool poissonErrors);
+
+/** pmPSFtryMetric()
+ * 
+ * This function is used to measure the PSF model metric for the set of
+ * results contained in the pmPSFtry structure.
+ * 
+ */
+bool pmPSFtryMetric(
+    pmPSFtry *psfTry,                  ///< Add comment.
+    float RADIUS                       ///< Add comment.
+);
+
+/** pmPSFtryMetric_Alt()
+ *
+ * This function is used to measure the PSF model metric for the set of
+ * results contained in the pmPSFtry structure (alternative implementation).
+ *
+ */
+bool pmPSFtryMetric_Alt(
+    pmPSFtry *try
+    ,                      ///< Add comment.
+    float RADIUS                        ///< Add comment.
+);
+
+# endif
Index: /tags/RHL_v1_1/psModules/src/objects/pmPeaks.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmPeaks.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmPeaks.c	(revision 9533)
@@ -0,0 +1,561 @@
+/** @file  pmPeaks.c
+ *
+ *  This file defines functions to detect and manipulate peaks in images
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA: significant modifications.
+ *
+ *  @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-15 09:49:01 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmPeaks.h"
+
+/******************************************************************************
+myListAddPeak(): A private function which allocates a psArray, if the list
+argument is NULL, otherwise it adds the peak to that list.
+XXX EAM : changed the output to psArray
+XXX EAM : Switched row, col args
+XXX EAM : NOTE: this was changed in the call, so the new code is consistent
+*****************************************************************************/
+static psArray *myListAddPeak(psArray *list,
+                              psS32 row,
+                              psS32 col,
+                              psF32 counts,
+                              pmPeakType type)
+{
+    psTrace("psModules.objects", 4, "---- %s() begin ----\n", __func__);
+    pmPeak *tmpPeak = pmPeakAlloc(col, row, counts, type);
+
+    if (list == NULL) {
+        list = psArrayAlloc(100);
+        list->n = 0;
+    }
+    psArrayAdd(list, 100, tmpPeak);
+    psFree (tmpPeak);
+    // XXX EAM : is this free appropriate?  (does psArrayAdd increment memory counter?)
+
+    psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+    return(list);
+}
+
+/******************************************************************************
+getRowVectorFromImage(): a private function which simply returns a
+psVector containing the specified row of data from the psImage.
+ 
+XXX: Is there a better way to do this?
+XXX EAM: does this really need to alloc a new vector???
+*****************************************************************************/
+static psVector *getRowVectorFromImage(psImage *image,
+                                       psU32 row)
+{
+    psTrace("psModules.objects", 4, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_IMAGE_NON_NULL(image, NULL);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, NULL);
+
+    psVector *tmpVector = psVectorAlloc(image->numCols, PS_TYPE_F32);
+    for (psU32 col = 0; col < image->numCols ; col++) {
+        tmpVector->data.F32[col] = image->data.F32[row][col];
+    }
+    tmpVector->n = image->numCols;
+    psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+    return(tmpVector);
+}
+
+/******************************************************************************
+isItInThisRegion(): a private function which simply returns a
+boolean denoting if specified coordinate is in the region.
+XXX: Macro this.
+*****************************************************************************/
+static bool isItInThisRegion(const psRegion valid,
+                             psS32 x,
+                             psS32 y)
+{
+    psTrace("psModules.objects", 4, "---- %s() begin ----\n", __func__);
+    if ((x >= valid.x0) &&
+            (x <= valid.x1) &&
+            (y >= valid.y0) &&
+            (y <= valid.y1)) {
+        psTrace("psModules.objects", 4, "---- %s(true) end ----\n", __func__);
+        return(true);
+    }
+    psTrace("psModules.objects", 4, "---- %s(false) end ----\n", __func__);
+    return(false);
+}
+
+/******************************************************************************
+pmPeakAlloc(): Allocate the pmPeak data structure and set appropriate members.
+*****************************************************************************/
+static void peakFree(pmPeak *tmp)
+{} // used by pmIsPeak()
+
+pmPeak *pmPeakAlloc(psS32 x,
+                    psS32 y,
+                    psF32 counts,
+                    pmPeakType type)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    static int id = 1;
+    pmPeak *tmp = (pmPeak *) psAlloc(sizeof(pmPeak));
+    *(int *)&tmp->id = id++;
+    tmp->x = x;
+    tmp->y = y;
+    tmp->counts = counts;
+    tmp->type = type;
+
+    psMemSetDeallocator(tmp, (psFreeFunc) peakFree);
+
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(tmp);
+}
+
+bool pmIsPeak(const psPtr ptr)
+{
+    return (psMemGetDeallocator(ptr) == (psFreeFunc)peakFree);
+}
+
+// psSort comparison function for peaks
+int pmPeaksCompareAscend (const void **a, const void **b)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    pmPeak *A = *(pmPeak **)a;
+    pmPeak *B = *(pmPeak **)b;
+
+    psF32 diff;
+
+    diff = A->counts - B->counts;
+    if (diff < FLT_EPSILON) {
+        psTrace("psModules.objects", 3, "---- %s(-1) end ----\n", __func__);
+        return (-1);
+    } else if (diff > FLT_EPSILON) {
+        psTrace("psModules.objects", 3, "---- %s(+1) end ----\n", __func__);
+        return (+1);
+    }
+    psTrace("psModules.objects", 3, "---- %s(0) end ----\n", __func__);
+    return (0);
+}
+
+// psSort comparison function for peaks
+int pmPeaksCompareDescend (const void **a, const void **b)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    pmPeak *A = *(pmPeak **)a;
+    pmPeak *B = *(pmPeak **)b;
+
+    psF32 diff;
+
+    diff = A->counts - B->counts;
+    if (diff < FLT_EPSILON) {
+        psTrace("psModules.objects", 3, "---- %s(+1) end ----\n", __func__);
+        return (+1);
+    } else if (diff > FLT_EPSILON) {
+        psTrace("psModules.objects", 3, "---- %s(-1) end ----\n", __func__);
+        return (-1);
+    }
+    psTrace("psModules.objects", 3, "---- %s(0) end ----\n", __func__);
+    return (0);
+}
+
+/******************************************************************************
+pmFindVectorPeaks(vector, threshold): Find all local peaks in the given vector
+above the given threshold.  Returns a vector of type PS_TYPE_U32 containing
+the location (x value) of all peaks.
+ 
+XXX: What types should be supported?  Only F32 is implemented.
+ 
+XXX: We currently step through the input vector twice; once to determine the
+size of the output vector, then to set the values of the output vector.
+Depending upon actual use, this may need to be optimized.
+*****************************************************************************/
+psVector *pmFindVectorPeaks(const psVector *vector,
+                            psF32 threshold)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_VECTOR_NON_NULL(vector, NULL);
+    PS_ASSERT_VECTOR_NON_EMPTY(vector, NULL);
+    PS_ASSERT_VECTOR_TYPE(vector, PS_TYPE_F32, NULL);
+    int count = 0;
+    int n = vector->n;
+
+    //
+    // Special case: the input vector has a single element.
+    //
+    if (n == 1) {
+        psVector *tmpVector = NULL;
+        ;
+        if (vector->data.F32[0] > threshold) {
+            tmpVector = psVectorAlloc(1, PS_TYPE_U32);
+            tmpVector->data.U32[0] = 0;
+            tmpVector->n = 1;
+        } else {
+            tmpVector = psVectorAlloc(0, PS_TYPE_U32);
+        }
+        psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+        return(tmpVector);
+    }
+
+    //
+    // Determine if first pixel is a peak
+    //
+    if ((vector->data.F32[0] > vector->data.F32[1]) &&
+            (vector->data.F32[0] > threshold)) {
+        count++;
+    }
+
+    //
+    // Determine if interior pixels are peaks
+    //
+    for (psU32 i = 1; i < n-1 ; i++) {
+        if ((vector->data.F32[i] > vector->data.F32[i-1]) &&
+                (vector->data.F32[i] >= vector->data.F32[i+1]) &&
+                (vector->data.F32[i] > threshold)) {
+            count++;
+        }
+    }
+
+    //
+    // Determine if last pixel is a peak
+    //
+    if ((vector->data.F32[n-1] > vector->data.F32[n-2]) &&
+            (vector->data.F32[n-1] > threshold)) {
+        count++;
+    }
+
+    //
+    // We know how many peaks exist, so we now allocate a psVector to store
+    // those peaks.
+    //
+    psVector *tmpVector = psVectorAlloc(count, PS_TYPE_U32);
+    tmpVector->n = count;
+    count = 0;
+
+    //
+    // Determine if first pixel is a peak
+    //
+    if ((vector->data.F32[0] > vector->data.F32[1]) &&
+            (vector->data.F32[0] > threshold)) {
+        tmpVector->data.U32[count++] = 0;
+    }
+
+    //
+    // Determine if interior pixels are peaks
+    //
+    for (psU32 i = 1; i < (n-1) ; i++) {
+        if ((vector->data.F32[i] > vector->data.F32[i-1]) &&
+                (vector->data.F32[i] >= vector->data.F32[i+1]) &&
+                (vector->data.F32[i] > threshold)) {
+            tmpVector->data.U32[count++] = i;
+        }
+    }
+
+    //
+    // Determine if last pixel is a peak
+    //
+    if ((vector->data.F32[n-1] > vector->data.F32[n-2]) &&
+            (vector->data.F32[n-1] > threshold)) {
+        tmpVector->data.U32[count++] = n-1;
+    }
+
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(tmpVector);
+}
+
+
+/******************************************************************************
+pmFindImagePeaks(image, threshold): Find all local peaks in the given psImage
+above the given threshold.  Returns a psArray containing location (x/y value)
+of all peaks.
+ 
+XXX: I'm not convinced the peak type definition in the SDRS is mutually
+exclusive.  Some peaks can have multiple types.  Edges for sure.  Also, a
+digonal line with the same value at each point will have a peak for every
+point on that line.
+ 
+XXX: This does not work if image has either a single row, or a single column.
+ 
+XXX: In the output psArray elements, should we use the image row/column offsets?
+     Currently, we do not.
+XXX EAM : this function needs to return peaks in *parent* coords
+ 
+XXX: Merge with CVS 1.20.  This had the proper code for images with a single
+row or column.
+ 
+*****************************************************************************/
+psArray *pmFindImagePeaks(const psImage *image,
+                          psF32 threshold)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_IMAGE_NON_NULL(image, NULL);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, NULL);
+    if ((image->numRows == 1) || (image->numCols == 1)) {
+        psError(PS_ERR_UNKNOWN, true, "Currently, input image must have at least 2 rows and 2 columns.");
+        psTrace("psModules.objects", 3, "---- %s(NULL) end ----\n", __func__);
+        return(NULL);
+    }
+    psVector *tmpRow = NULL;
+    psU32 col = 0;
+    psU32 row = 0;
+    psArray *list = NULL;
+
+    psU32 col0 = image->col0;
+    psU32 row0 = image->row0;
+
+    //
+    // Find peaks in row 0 only.
+    //
+    row = 0;
+    tmpRow = getRowVectorFromImage((psImage *) image, row);
+    psVector *row1 = pmFindVectorPeaks(tmpRow, threshold);
+    // pmFindVectorPeaks returns coords in the vector, not corrected for col0
+
+    for (psU32 i = 0 ; i < row1->n ; i++ ) {
+        col = row1->data.U32[i];
+        //
+        // Determine if pixel (0,0) is a peak.
+        //
+        if (col == 0) {
+            if ( (image->data.F32[row][col] >  image->data.F32[row][col+1]) &&
+                    (image->data.F32[row][col] >  image->data.F32[row+1][col]) &&
+                    (image->data.F32[row][col] >= image->data.F32[row+1][col+1])) {
+
+                if (image->data.F32[row][col] > threshold) {
+                    list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], PM_PEAK_EDGE);
+                }
+            }
+        } else if (col < (image->numCols - 1)) {
+            if ( (image->data.F32[row][col] >= image->data.F32[row][col-1]) &&
+                    (image->data.F32[row][col] >  image->data.F32[row][col+1]) &&
+                    (image->data.F32[row][col] >= image->data.F32[row+1][col-1]) &&
+                    (image->data.F32[row][col] >  image->data.F32[row+1][col]) &&
+                    (image->data.F32[row][col] >= image->data.F32[row+1][col+1])) {
+                if (image->data.F32[row][col] > threshold) {
+                    list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], PM_PEAK_EDGE);
+                }
+            }
+
+        } else if (col == (image->numCols - 1)) {
+            if ( (image->data.F32[row][col] >= image->data.F32[row][col-1]) &&
+                    (image->data.F32[row][col] > image->data.F32[row+1][col]) &&
+                    (image->data.F32[row][col] >= image->data.F32[row+1][col-1])) {
+                if (image->data.F32[row][col] > threshold) {
+                    list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], PM_PEAK_EDGE);
+                }
+            }
+
+        } else {
+            psError(PS_ERR_UNKNOWN, true, "peak specified valid column range.");
+        }
+    }
+    psFree (tmpRow);
+    psFree (row1);
+
+    //
+    // Exit if this image has a single row.
+    //
+    if (image->numRows == 1) {
+        psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+        return(list);
+    }
+
+    //
+    // Find peaks in interior rows only.
+    //
+    for (row = 1 ; row < (image->numRows - 1) ; row++) {
+        tmpRow = getRowVectorFromImage((psImage *) image, row);
+        row1 = pmFindVectorPeaks(tmpRow, threshold);
+
+        // Step through all local peaks in this row.
+        for (psU32 i = 0 ; i < row1->n ; i++ ) {
+            pmPeakType myType = PM_PEAK_UNDEF;
+            col = row1->data.U32[i];
+
+            if (col == 0) {
+                // If col==0, then we can not read col-1 pixels
+                if ((image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row-1][col+1]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row][col+1]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row+1][col]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row+1][col+1])) {
+                    myType = PM_PEAK_EDGE;
+                    list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], myType);
+                }
+            } else if (col < (image->numCols - 1)) {
+                // This is an interior pixel
+                if ((image->data.F32[row][col] >= image->data.F32[row-1][col-1]) &&
+                        (image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row-1][col+1]) &&
+                        (image->data.F32[row][col] > image->data.F32[row][col-1]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row][col+1]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row+1][col-1]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row+1][col]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row+1][col+1])) {
+                    if (image->data.F32[row][col] > threshold) {
+                        if ((image->data.F32[row][col] > image->data.F32[row-1][col-1]) &&
+                                (image->data.F32[row][col] > image->data.F32[row-1][col]) &&
+                                (image->data.F32[row][col] > image->data.F32[row-1][col+1]) &&
+                                (image->data.F32[row][col] > image->data.F32[row][col-1]) &&
+                                (image->data.F32[row][col] > image->data.F32[row][col+1]) &&
+                                (image->data.F32[row][col] > image->data.F32[row+1][col-1]) &&
+                                (image->data.F32[row][col] > image->data.F32[row+1][col]) &&
+                                (image->data.F32[row][col] > image->data.F32[row+1][col+1])) {
+                            myType = PM_PEAK_LONE;
+                        }
+
+                        if ((image->data.F32[row][col] == image->data.F32[row-1][col-1]) ||
+                                (image->data.F32[row][col] == image->data.F32[row-1][col]) ||
+                                (image->data.F32[row][col] == image->data.F32[row-1][col+1]) ||
+                                (image->data.F32[row][col] == image->data.F32[row][col-1]) ||
+                                (image->data.F32[row][col] == image->data.F32[row][col+1]) ||
+                                (image->data.F32[row][col] == image->data.F32[row+1][col-1]) ||
+                                (image->data.F32[row][col] == image->data.F32[row+1][col]) ||
+                                (image->data.F32[row][col] == image->data.F32[row+1][col+1])) {
+                            myType = PM_PEAK_FLAT;
+                        }
+
+                        list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], myType);
+                    }
+                }
+            } else if (col == (image->numCols - 1)) {
+                // If col==numCols - 1, then we can not read col+1 pixels
+                if ((image->data.F32[row][col] >= image->data.F32[row-1][col-1]) &&
+                        (image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+                        (image->data.F32[row][col] > image->data.F32[row][col-1]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row][col+1]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row+1][col-1]) &&
+                        (image->data.F32[row][col] >= image->data.F32[row+1][col])) {
+                    myType = PM_PEAK_EDGE;
+                    list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], myType);
+                }
+            } else {
+                psError(PS_ERR_UNKNOWN, true, "peak specified outside valid column range.");
+            }
+
+        }
+        psFree (tmpRow);
+        psFree (row1);
+    }
+
+    //
+    // Find peaks in the last row only.
+    //
+    row = image->numRows - 1;
+    tmpRow = getRowVectorFromImage((psImage *) image, row);
+    row1 = pmFindVectorPeaks(tmpRow, threshold);
+    for (psU32 i = 0 ; i < row1->n ; i++ ) {
+        col = row1->data.U32[i];
+        if (col == 0) {
+            if ( (image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+                    (image->data.F32[row][col] >= image->data.F32[row-1][col+1]) &&
+                    (image->data.F32[row][col] >  image->data.F32[row][col+1])) {
+                if (image->data.F32[row][col] > threshold) {
+                    list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], PM_PEAK_EDGE);
+                }
+            }
+        } else if (col < (image->numCols - 1)) {
+            if ( (image->data.F32[row][col] >= image->data.F32[row-1][col-1]) &&
+                    (image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+                    (image->data.F32[row][col] >= image->data.F32[row-1][col+1]) &&
+                    (image->data.F32[row][col] >  image->data.F32[row][col-1]) &&
+                    (image->data.F32[row][col] >= image->data.F32[row][col+1])) {
+                if (image->data.F32[row][col] > threshold) {
+                    list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], PM_PEAK_EDGE);
+                }
+            }
+
+        } else if (col == (image->numCols - 1)) {
+            if ( (image->data.F32[row][col] >= image->data.F32[row-1][col-1]) &&
+                    (image->data.F32[row][col] >  image->data.F32[row-1][col]) &&
+                    (image->data.F32[row][col] >  image->data.F32[row][col-1])) {
+                if (image->data.F32[row][col] > threshold) {
+                    list = myListAddPeak(list, row + row0, col + col0, image->data.F32[row][col], PM_PEAK_EDGE);
+                }
+            }
+        } else {
+            psError(PS_ERR_UNKNOWN, true, "peak specified outside valid column range.");
+        }
+    }
+    psFree (tmpRow);
+    psFree (row1);
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(list);
+}
+
+
+/******************************************************************************
+psCullPeaks(peaks, maxValue, valid): eliminate peaks from the psArray that have
+a peak value above the given maximum, or fall outside the valid region.
+ 
+XXX: Should the sky value be used when comparing the maximum?
+ 
+XXX: warning message if valid is NULL?
+ 
+XXX: changed API to create a NEW output psArray (should change name as well)
+ 
+XXX: Do we free the psList elements of those culled peaks?
+ 
+XXX EAM : do we still need pmCullPeaks, or only pmPeaksSubset?
+*****************************************************************************/
+psList *pmCullPeaks(psList *peaks,
+                    psF32 maxValue,
+                    const psRegion valid)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(peaks, NULL);
+
+    psListElem *tmpListElem = (psListElem *) peaks->head;
+    psS32 indexNum = 0;
+
+    //    printf("pmCullPeaks(): list size is %d\n", peaks->size);
+    while (tmpListElem != NULL) {
+        pmPeak *tmpPeak = (pmPeak *) tmpListElem->data;
+        if ((tmpPeak->counts > maxValue) ||
+                (true == isItInThisRegion(valid, tmpPeak->x, tmpPeak->y))) {
+            psListRemoveData(peaks, (psPtr) tmpPeak);
+        }
+
+        indexNum++;
+        tmpListElem = tmpListElem->next;
+    }
+
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(peaks);
+}
+
+// XXX EAM: I changed this to return a new, subset array
+//          rather than alter the existing one
+// XXX: Fix the *valid pointer.
+psArray *pmPeaksSubset(
+    psArray *peaks,
+    psF32 maxValue,
+    const psRegion valid)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(peaks, NULL);
+
+    psArray *output = psArrayAlloc (200);
+    output->n = 0;
+
+    psTrace (".pmObjects.pmCullPeaks", 3, "list size is %ld\n", peaks->n);
+
+    for (int i = 0; i < peaks->n; i++) {
+        pmPeak *tmpPeak = (pmPeak *) peaks->data[i];
+        if (tmpPeak->counts > maxValue)
+            continue;
+        if (isItInThisRegion(valid, tmpPeak->x, tmpPeak->y))
+            continue;
+        psArrayAdd (output, 200, tmpPeak);
+    }
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(output);
+}
Index: /tags/RHL_v1_1/psModules/src/objects/pmPeaks.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmPeaks.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmPeaks.h	(revision 9533)
@@ -0,0 +1,145 @@
+/** @file  pmPeaks.h
+ *
+ * The process of finding, measuring, and classifying astronomical sources on
+ * images is one of the critical tasks of the IPP or any astronomical software
+ * system. This file will define structures and functions related to the task
+ * of source detection and measurement. The elements defined in this section
+ * are generally low-level components which can be connected together to
+ * construct a complete object measurement suite.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-06-03 01:02:08 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+# ifndef PM_PEAKS_H
+# define PM_PEAKS_H
+
+/** pmPeakType
+ *
+ *  A peak pixel may have several features which may be determined when the
+ *  peak is found or measured. These are specified by the pmPeakType enum.
+ *  PM_PEAK_LONE represents a single pixel which is higher than its 8 immediate
+ *  neighbors.  The PM_PEAK_EDGE represents a peak pixel which touching the image
+ *  edge. The PM_PEAK_FLAT represents a peak pixel which has more than a specific
+ *  number of neighbors at the same value, within some tolarence:
+ *
+ */
+typedef enum {
+    PM_PEAK_LONE,                       ///< Isolated peak.
+    PM_PEAK_EDGE,                       ///< Peak on edge.
+    PM_PEAK_FLAT,                       ///< Peak has equal-value neighbors.
+    PM_PEAK_UNDEF                       ///< Undefined.
+} pmPeakType;
+
+
+/** pmPeak data structure
+ *
+ *  A source has the capacity for several types of measurements. The
+ *  simplest measurement of a source is the location and flux of the peak pixel
+ *  associated with the source:
+ *
+ */
+typedef struct
+{
+    const int id;   ///< Unique ID for object
+    int x;                              ///< X-coordinate of peak pixel.
+    int y;                              ///< Y-coordinate of peak pixel.
+    float counts;                       ///< Value of peak pixel (above sky?).
+    pmPeakType type;                   ///< Description of peak.
+}
+pmPeak;
+
+
+/** pmPeakAlloc()
+ *
+ *  @return pmPeak*    newly allocated pmPeak with all internal pointers set to NULL
+ */
+pmPeak *pmPeakAlloc(
+    int x,    ///< Row-coordinate in image space
+    int y,    ///< Col-coordinate in image space
+    float counts,   ///< The value of the peak pixel
+    pmPeakType type   ///< The type of peak pixel
+);
+
+bool pmIsPeak(const psPtr ptr);
+
+/** pmFindVectorPeaks()
+ *
+ * Find all local peaks in the given vector above the given threshold. A peak
+ * is defined as any element with a value greater than its two neighbors and with
+ * a value above the threshold. Two types of special cases must be addressed.
+ * Equal value elements: If an element has the same value as the following
+ * element, it is not considered a peak. If an element has the same value as the
+ * preceding element (but not the following), then it is considered a peak. Note
+ * that this rule (arbitrarily) identifies flat regions by their trailing edge.
+ * Edge cases: At start of the vector, the element must be higher than its
+ * neighbor. At the end of the vector, the element must be higher or equal to its
+ * neighbor. These two rules again places the peak associated with a flat region
+ * which touches the image edge at the image edge. The result of this function is
+ * a vector containing the coordinates (element number) of the detected peaks
+ * (type psU32).
+ *
+ */
+psVector *pmFindVectorPeaks(
+    const psVector *vector,  ///< The input vector (float)
+    float threshold   ///< Threshold above which to find a peak
+);
+
+
+/** pmFindImagePeaks()
+ *
+ * Find all local peaks in the given image above the given threshold. This
+ * function should find all row peaks using pmFindVectorPeaks, then test each row
+ * peak and exclude peaks which are not local peaks. A peak is a local peak if it
+ * has a higher value than all 8 neighbors. If the peak has the same value as its
+ * +y neighbor or +x neighbor, it is NOT a local peak. If any other neighbors
+ * have an equal value, the peak is considered a valid peak. Note two points:
+ * first, the +x neighbor condition is already enforced by pmFindVectorPeaks.
+ * Second, these rules have the effect of making flat-topped regions have single
+ * peaks at the (+x,+y) corner. When selecting the peaks, their type must also be
+ * set. The result of this function is an array of pmPeak entries.
+ *
+ */
+psArray *pmFindImagePeaks(
+    const psImage *image,  ///< The input image where peaks will be found (float)
+    float threshold   ///< Threshold above which to find a peak
+);
+
+
+/** pmCullPeaks()
+ *
+ * Eliminate peaks from the psList that have a peak value above the given
+ * maximum, or fall outside the valid region.
+ *
+ */
+psList *pmCullPeaks(
+    psList *peaks,   ///< The psList of peaks to be culled
+    float maxValue,   ///< Cull peaks above this value
+    const psRegion valid                ///< Cull peaks otside this psRegion
+);
+
+
+/** pmPeaksSubset()
+ *
+ * Create a new peaks array, removing certain types of peaks from the input
+ * array of peaks based on the given criteria. Peaks should be eliminated if they
+ * have a peak value above the given maximum value limit or if the fall outside
+ * the valid region.  The result of the function is a new array with a reduced
+ * number of peaks.
+ *
+ */
+psArray *pmPeaksSubset(
+    psArray *peaks,                     ///< Add comment.
+    float maxvalue,                     ///< Add comment.
+    const psRegion valid                ///< Add comment.
+);
+
+int pmPeaksCompareAscend (const void **a, const void **b);
+int pmPeaksCompareDescend (const void **a, const void **b);
+
+# endif /* PM_PEAKS_H */
Index: /tags/RHL_v1_1/psModules/src/objects/pmSource.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSource.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSource.c	(revision 9533)
@@ -0,0 +1,664 @@
+/** @file  pmSource.c
+ *
+ *  Functions to define and manipulate sources on images
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA: significant modifications.
+ *
+ *  @version $Revision: 1.13 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-15 09:49:01 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmModel.h"
+#include "pmSource.h"
+
+static void sourceFree(pmSource *tmp)
+{
+    psTrace("psModules.objects", 4, "---- %s() begin ----\n", __func__);
+    psFree(tmp->peak);
+    psFree(tmp->pixels);
+    psFree(tmp->weight);
+    psFree(tmp->mask);
+    psFree(tmp->moments);
+    psFree(tmp->modelPSF);
+    psFree(tmp->modelEXT);
+    psFree(tmp->blends);
+    psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+}
+
+void pmSourceFreePixels(pmSource *source)
+{
+
+    if (!source)
+        return;
+
+    psFree (source->pixels);
+    psFree (source->weight);
+    psFree (source->mask);
+
+    source->pixels = NULL;
+    source->weight = NULL;
+    source->mask = NULL;
+    return;
+}
+
+/******************************************************************************
+pmSourceAlloc(): Allocate the pmSource structure and initialize its members
+to NULL.
+*****************************************************************************/
+pmSource *pmSourceAlloc()
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    static int id = 1;
+    pmSource *tmp = (pmSource *) psAlloc(sizeof(pmSource));
+    *(int *)&tmp->id = id++;
+    tmp->peak = NULL;
+    tmp->pixels = NULL;
+    tmp->weight = NULL;
+    tmp->mask = NULL;
+    tmp->moments = NULL;
+    tmp->blends = NULL;
+    tmp->modelPSF = NULL;
+    tmp->modelEXT = NULL;
+    tmp->type = PM_SOURCE_TYPE_UNKNOWN;
+    tmp->mode = PM_SOURCE_MODE_DEFAULT;
+    psMemSetDeallocator(tmp, (psFreeFunc) sourceFree);
+
+    tmp->psfMag = 32.0;
+    tmp->extMag = 32.0;
+    tmp->errMag = 32.0;
+    tmp->apMag  = 32.0;
+    tmp->pixWeight = 0.0;
+
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(tmp);
+}
+
+bool pmIsSource(const psPtr ptr)
+{
+    return (psMemGetDeallocator(ptr) == (psFreeFunc)sourceFree);
+}
+
+// x,y are defined in the parent image coords of readout->image
+bool pmSourceDefinePixels(pmSource *mySource,
+                          const pmReadout *readout,
+                          psF32 x,
+                          psF32 y,
+                          psF32 Radius)
+{
+    psRegion srcRegion;
+
+    // Grab a subimage of the original image of size (2 * outerRadius).
+    srcRegion = psRegionForSquare (x, y, Radius);
+    srcRegion = psRegionForImage (readout->image, srcRegion);
+
+    mySource->pixels = psMemIncrRefCounter(psImageSubset(readout->image, srcRegion));
+    mySource->weight = psMemIncrRefCounter(psImageSubset(readout->weight, srcRegion));
+    mySource->mask   = psMemIncrRefCounter(psImageSubset(readout->mask,  srcRegion));
+    mySource->region = srcRegion;
+
+    return true;
+}
+
+bool pmSourceRedefinePixels(pmSource *mySource,
+                            const pmReadout *readout,
+                            psF32 x,
+                            psF32 y,
+                            psF32 Radius)
+{
+    bool extend;
+    psRegion newRegion;
+
+    if (Radius == 0)
+        return false;
+
+    // check to see if new region is completely contained within old region
+    newRegion = psRegionForSquare (x, y, Radius);
+    newRegion = psRegionForImage (readout->image, newRegion);
+
+    extend = false;
+    extend |= (int)(newRegion.x0) < (int)(mySource->region.x0);
+    extend |= (int)(newRegion.x1) > (int)(mySource->region.x1);
+    extend |= (int)(newRegion.y0) < (int)(mySource->region.y0);
+    extend |= (int)(newRegion.y1) > (int)(mySource->region.y1);
+
+    extend |= (mySource->pixels == NULL);
+    extend |= (mySource->weight == NULL);
+    extend |= (mySource->mask == NULL);
+
+    // extend = true;
+    if (extend) {
+        // Grab a new subimage
+        psFree (mySource->pixels);
+        psFree (mySource->weight);
+        psFree (mySource->mask);
+
+        mySource->pixels = psMemIncrRefCounter(psImageSubset(readout->image,  newRegion));
+        mySource->weight = psMemIncrRefCounter(psImageSubset(readout->weight, newRegion));
+        mySource->mask   = psMemIncrRefCounter(psImageSubset(readout->mask,   newRegion));
+        mySource->region = newRegion;
+    }
+
+    return extend;
+}
+
+/******************************************************************************
+    pmSourcePSFClump(source, metadata): Find the likely PSF clump in the
+    sigma-x, sigma-y plane. return 0,0 clump in case of error.
+*****************************************************************************/
+
+// XXX EAM include a S/N cutoff in selecting the sources?
+pmPSFClump pmSourcePSFClump(psArray *sources, psMetadata *metadata)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+
+    # define NPIX 10
+    # define SCALE 0.1
+
+    psArray *peaks  = NULL;
+    pmPSFClump emptyClump = {0.0, 0.0, 0.0, 0.0};
+    pmPSFClump psfClump = emptyClump;
+
+    PS_ASSERT_PTR_NON_NULL(sources, emptyClump);
+    PS_ASSERT_PTR_NON_NULL(metadata, emptyClump);
+
+    // find the sigmaX, sigmaY clump
+    {
+        psStats *stats  = NULL;
+        psImage *splane = NULL;
+        int binX, binY;
+        bool status;
+
+        psF32 SX_MAX = psMetadataLookupF32 (&status, metadata, "MOMENTS_SX_MAX");
+        if (!status)
+            SX_MAX = 10.0;
+        psF32 SY_MAX = psMetadataLookupF32 (&status, metadata, "MOMENTS_SY_MAX");
+        if (!status)
+            SY_MAX = 10.0;
+        psF32 AR_MAX = psMetadataLookupF32 (&status, metadata, "MOMENTS_AR_MAX");
+        if (!status)
+            AR_MAX =  3.0;
+        psF32 AR_MIN = 1.0 / AR_MAX;
+
+        // construct a sigma-plane image
+        // psImageAlloc does zero the data
+        splane = psImageAlloc (SX_MAX/SCALE, SY_MAX/SCALE, PS_TYPE_F32);
+        for (int i = 0; i < splane->numRows; i++)
+        {
+            memset (splane->data.F32[i], 0, splane->numCols*sizeof(PS_TYPE_F32));
+        }
+
+        // place the sources in the sigma-plane image (ignore 0,0 values?)
+        for (psS32 i = 0 ; i < sources->n ; i++)
+        {
+            pmSource *tmpSrc = (pmSource *) sources->data[i];
+            if (tmpSrc == NULL) {
+                continue;
+            }
+            if (tmpSrc->moments == NULL) {
+                continue;
+            }
+
+            // Sx,Sy are limited at 0.  a peak at 0,0 is artificial
+            if (fabs(tmpSrc->moments->Sx) < 0.05) {
+                continue;
+            }
+            if (fabs(tmpSrc->moments->Sy) < 0.05) {
+                continue;
+            }
+            if ((tmpSrc->moments->Sx / tmpSrc->moments->Sy) > AR_MAX) {
+                continue;
+            }
+            if ((tmpSrc->moments->Sx / tmpSrc->moments->Sy) < AR_MIN) {
+                continue;
+            }
+            if (tmpSrc->mode & PM_SOURCE_MODE_BLEND) {
+                continue;
+            }
+
+            // for the moment, force splane dimensions to be 10x10 image pix
+            binX = tmpSrc->moments->Sx/SCALE;
+            if (binX < 0)
+                continue;
+            if (binX >= splane->numCols)
+                continue;
+
+            binY = tmpSrc->moments->Sy/SCALE;
+            if (binY < 0)
+                continue;
+            if (binY >= splane->numRows)
+                continue;
+
+            splane->data.F32[binY][binX] += 1.0;
+        }
+
+        // find the peak in this image
+        stats = psStatsAlloc (PS_STAT_MAX);
+        stats = psImageStats (stats, splane, NULL, 0);
+        peaks = pmFindImagePeaks (splane, stats[0].max / 2);
+        psTrace (".pmObjects.pmSourceRoughClass", 2, "clump threshold is %f\n", stats[0].max/2);
+
+        const bool keep_psf_clump = psMetadataLookupBool(NULL, metadata, "KEEP_PSF_CLUMP");
+        if (keep_psf_clump)
+        {
+            psMetadataAdd(metadata, PS_LIST_TAIL,
+                          "PSF_CLUMP", PS_DATA_IMAGE, "Image of PSF coefficients", splane);
+        }
+        psFree (splane);
+        psFree (stats);
+
+        if (peaks == NULL)
+        {
+            return (psfClump);
+        }
+    }
+    // XXX EAM : possible errors:
+    //           1) no peak in splane
+    //           2) no significant peak in splane
+
+    // measure statistics on Sx, Sy if Sx, Sy within range of clump
+    {
+        pmPeak *clump;
+        psF32 minSx, maxSx;
+        psF32 minSy, maxSy;
+        psVector *tmpSx = NULL;
+        psVector *tmpSy = NULL;
+        psStats *stats  = NULL;
+
+        // XXX EAM : this lets us takes the single highest peak
+        psArraySort (peaks, pmPeaksCompareDescend);
+        clump = peaks->data[0];
+        psTrace (".pmObjects.pmSourceRoughClass", 2, "clump is at %d, %d (%f)\n", clump->x, clump->y, clump->counts);
+
+        // define section window for clump
+        minSx = clump->x * SCALE - 0.2;
+        maxSx = clump->x * SCALE + 0.2;
+        minSy = clump->y * SCALE - 0.2;
+        maxSy = clump->y * SCALE + 0.2;
+
+        tmpSx = psVectorAlloc (sources->n, PS_TYPE_F32);
+        tmpSy = psVectorAlloc (sources->n, PS_TYPE_F32);
+        tmpSx->n = 0;
+        tmpSy->n = 0;
+
+        // XXX clip sources based on flux?
+        // create vectors with Sx, Sy values in window
+        for (psS32 i = 0 ; i < sources->n ; i++)
+        {
+            pmSource *tmpSrc = (pmSource *) sources->data[i];
+
+            if (tmpSrc->moments->Sx < minSx)
+                continue;
+            if (tmpSrc->moments->Sx > maxSx)
+                continue;
+            if (tmpSrc->moments->Sy < minSy)
+                continue;
+            if (tmpSrc->moments->Sy > maxSy)
+                continue;
+            tmpSx->data.F32[tmpSx->n] = tmpSrc->moments->Sx;
+            tmpSy->data.F32[tmpSy->n] = tmpSrc->moments->Sy;
+            tmpSx->n++;
+            tmpSy->n++;
+            if (tmpSx->n == tmpSx->nalloc) {
+                psVectorRealloc (tmpSx, tmpSx->nalloc + 100);
+                psVectorRealloc (tmpSy, tmpSy->nalloc + 100);
+            }
+        }
+
+        // measures stats of Sx, Sy
+        stats = psStatsAlloc (PS_STAT_CLIPPED_MEAN | PS_STAT_CLIPPED_STDEV);
+
+        stats = psVectorStats (stats, tmpSx, NULL, NULL, 0);
+        psfClump.X  = stats->clippedMean;
+        psfClump.dX = stats->clippedStdev;
+
+        stats = psVectorStats (stats, tmpSy, NULL, NULL, 0);
+        psfClump.Y  = stats->clippedMean;
+        psfClump.dY = stats->clippedStdev;
+
+        psTrace (".pmObjects.pmSourceRoughClass", 2, "clump  X,  Y: %f, %f\n", psfClump.X, psfClump.Y);
+        psTrace (".pmObjects.pmSourceRoughClass", 2, "clump DX, DY: %f, %f\n", psfClump.dX, psfClump.dY);
+        // these values should be pushed on the metadata somewhere
+
+        psFree (stats);
+        psFree (peaks);
+        psFree (tmpSx);
+        psFree (tmpSy);
+    }
+
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return (psfClump);
+}
+
+/******************************************************************************
+    pmSourceRoughClass(source, metadata): make a guess at the source
+    classification.
+ 
+    XXX: push the clump info into the metadata?
+ 
+    XXX: How can this function ever return FALSE?
+ 
+    EAM: I moved S/N calculation to pmSourceMoments, using weight image
+*****************************************************************************/
+
+bool pmSourceRoughClass(psArray *sources, psMetadata *metadata, pmPSFClump clump)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+
+    psBool rc = true;
+
+    int Nsat     = 0;
+    int Next     = 0;
+    int Nstar    = 0;
+    int Npsf     = 0;
+    int Ncr      = 0;
+    int Nsatstar = 0;
+    // psRegion allArray = psRegionSet (0, 0, 0, 0);
+    psRegion inner;
+
+    // report stats on S/N values for star-like objects
+    psVector *starsn = psVectorAlloc (sources->n, PS_TYPE_F32);
+    starsn->n = 0;
+
+    // check return status value (do these exist?)
+    bool status;
+    psF32 PSF_SN_LIM = psMetadataLookupF32 (&status, metadata, "PSF_SN_LIM");
+
+    // XXX allow clump size to be scaled relative to sigmas?
+    // make rough IDs based on clumpX,Y,DX,DY
+    for (psS32 i = 0 ; i < sources->n ; i++) {
+
+        pmSource *tmpSrc = (pmSource *) sources->data[i];
+
+        tmpSrc->peak->type = 0;
+
+        psF32 sigX = tmpSrc->moments->Sx;
+        psF32 sigY = tmpSrc->moments->Sy;
+
+        // XXX EAM : can we use the value of SATURATE if mask is NULL?
+        // inner = psRegionForSquare (tmpSrc->peak->x - tmpSrc->mask->col0, tmpSrc->peak->y - tmpSrc->mask->row0, 2);
+        inner = psRegionForSquare (tmpSrc->peak->x, tmpSrc->peak->y, 2);
+        inner = psRegionForImage (tmpSrc->mask, inner);
+        int Nsatpix = psImageCountPixelMask (tmpSrc->mask, inner, PM_MASK_SAT);
+
+        // saturated star (size consistent with PSF or larger)
+        // Nsigma should be user-configured parameter
+        bool big = (sigX > (clump.X - clump.dX)) && (sigY > (clump.Y - clump.dY));
+        big = true;
+        if ((Nsatpix > 1) && big) {
+            tmpSrc->type = PM_SOURCE_TYPE_STAR;
+            tmpSrc->mode |= PM_SOURCE_MODE_SATSTAR;
+            Nsatstar ++;
+            continue;
+        }
+
+        // saturated object (not a star, eg bleed trails, hot pixels)
+        if (Nsatpix > 1) {
+            tmpSrc->type = PM_SOURCE_TYPE_SATURATED;
+            tmpSrc->mode |= PM_SOURCE_MODE_DEFAULT;
+            Nsat ++;
+            continue;
+        }
+
+        // likely defect (too small to be stellar) (push out to 3 sigma)
+        // low S/N objects which are small are probably stellar
+        // only set candidate defects if
+        if ((sigX < 0.05) || (sigY < 0.05)) {
+            tmpSrc->type = PM_SOURCE_TYPE_DEFECT;
+            tmpSrc->mode |= PM_SOURCE_MODE_DEFAULT;
+            Ncr ++;
+            continue;
+        }
+
+        // likely unsaturated extended source (too large to be stellar)
+        if ((sigX > (clump.X + 3*clump.dX)) || (sigY > (clump.Y + 3*clump.dY))) {
+            tmpSrc->type = PM_SOURCE_TYPE_EXTENDED;
+            tmpSrc->mode |= PM_SOURCE_MODE_DEFAULT;
+            Next ++;
+            continue;
+        }
+
+        // the rest are probable stellar objects
+        starsn->data.F32[starsn->n] = tmpSrc->moments->SN;
+        starsn->n ++;
+        Nstar ++;
+
+        // PSF star (within 1.5 sigma of clump center, S/N > limit)
+        psF32 radius = hypot ((sigX-clump.X)/clump.dX, (sigY-clump.Y)/clump.dY);
+        if ((tmpSrc->moments->SN > PSF_SN_LIM) && (radius < 1.5)) {
+            tmpSrc->type = PM_SOURCE_TYPE_STAR;
+            tmpSrc->mode |= PM_SOURCE_MODE_PSFSTAR;
+            Npsf ++;
+            continue;
+        }
+
+        // random type of star
+        tmpSrc->type = PM_SOURCE_TYPE_STAR;
+        tmpSrc->mode |= PM_SOURCE_MODE_DEFAULT;
+    }
+
+    {
+        psStats *stats  = NULL;
+        stats = psStatsAlloc (PS_STAT_MIN | PS_STAT_MAX);
+        stats = psVectorStats (stats, starsn, NULL, NULL, 0);
+        psLogMsg ("pmObjects", 3, "SN range: %f - %f\n", stats[0].min, stats[0].max);
+        psFree (stats);
+        psFree (starsn);
+    }
+
+    psTrace (".pmObjects.pmSourceRoughClass", 2, "Nstar:    %3d\n", Nstar);
+    psTrace (".pmObjects.pmSourceRoughClass", 2, "Npsf:     %3d\n", Npsf);
+    psTrace (".pmObjects.pmSourceRoughClass", 2, "Next:     %3d\n", Next);
+    psTrace (".pmObjects.pmSourceRoughClass", 2, "Nsatstar: %3d\n", Nsatstar);
+    psTrace (".pmObjects.pmSourceRoughClass", 2, "Nsat:     %3d\n", Nsat);
+    psTrace (".pmObjects.pmSourceRoughClass", 2, "Ncr:      %3d\n", Ncr);
+
+    psTrace("psModules.objects", 3, "---- %s(%d) end ----\n", __func__, rc);
+    return(rc);
+}
+
+/******************************************************************************
+pmSourceMoments(source, radius): this function takes a subImage defined in the
+pmSource data structure, along with the peak location, and determines the
+various moments associated with that peak.
+ 
+Requires the following to have been created:
+    pmSource
+    pmSource->peak
+    pmSource->pixels
+    pmSource->weight
+    pmSource->mask
+ 
+XXX: The peak calculations are done in image coords, not subImage coords.
+ 
+XXX EAM : this version clips input pixels on S/N
+XXX EAM : this version returns false for several reasons
+*****************************************************************************/
+# define VALID_RADIUS(X,Y,RAD2) (((RAD2) >= (PS_SQR(X) + PS_SQR(Y))) ? 1 : 0)
+
+bool pmSourceMoments(pmSource *source,
+                     psF32 radius)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_PTR_NON_NULL(source->mask, false);
+    PS_ASSERT_FLOAT_LARGER_THAN(radius, 0.0, false);
+
+    //
+    // XXX: Verify the setting for sky if source->moments == NULL.
+    //
+    psF32 sky = 0.0;
+    if (source->moments == NULL) {
+        source->moments = pmMomentsAlloc();
+    } else {
+        sky = source->moments->Sky;
+    }
+
+    //
+    // Sum = SUM (z - sky)
+    // X1  = SUM (x - xc)*(z - sky)
+    // X2  = SUM (x - xc)^2 * (z - sky)
+    // XY  = SUM (x - xc)*(y - yc)*(z - sky)
+    //
+    psF32 peakPixel = -PS_MAX_F32;
+    psS32 numPixels = 0;
+    psF32 Sum = 0.0;
+    psF32 Var = 0.0;
+    psF32 X1 = 0.0;
+    psF32 Y1 = 0.0;
+    psF32 X2 = 0.0;
+    psF32 Y2 = 0.0;
+    psF32 XY = 0.0;
+    psF32 x  = 0;
+    psF32 y  = 0;
+    psF32 R2 = PS_SQR(radius);
+
+    psF32 xPeak = source->peak->x;
+    psF32 yPeak = source->peak->y;
+    psF32 xOff = source->pixels->col0 - source->peak->x;
+    psF32 yOff = source->pixels->row0 - source->peak->y;
+
+    // XXX why do I get different results for these two methods of finding Sx?
+    // XXX Sx, Sy would be better measured if we clip pixels close to sky
+    // XXX Sx, Sy can still be imaginary, so we probably need to keep Sx^2?
+    // We loop through all pixels in this subimage (source->pixels), and for each
+    // pixel that is not masked, AND within the radius of the peak pixel, we
+    // proceed with the moments calculation.  need to do two loops for a
+    // numerically stable result.  first loop: get the sums.
+    // XXX EAM : mask == 0 is valid
+
+    for (psS32 row = 0; row < source->pixels->numRows ; row++) {
+
+        psF32 *vPix = source->pixels->data.F32[row];
+        psF32 *vWgt = source->weight->data.F32[row];
+        psU8  *vMsk = (source->mask == NULL) ? NULL : source->mask->data.U8[row];
+
+        for (psS32 col = 0; col < source->pixels->numCols ; col++, vPix++, vWgt++) {
+            if ((vMsk != NULL) && *vMsk) {
+                vMsk++;
+                continue;
+            }
+
+            psF32 xDiff = col + xOff;
+            psF32 yDiff = row + yOff;
+
+            // radius is just a function of (xDiff, yDiff)
+            if (!VALID_RADIUS(xDiff, yDiff, R2)) {
+                if (vMsk != NULL)
+                    vMsk++;
+                continue;
+            }
+
+            psF32 pDiff = *vPix - sky;
+            psF32 wDiff = *vWgt;
+
+            // XXX EAM : check for valid S/N in pixel
+            // XXX EAM : should this limit be user-defined?
+            if (PS_SQR(pDiff) < wDiff) {
+                if (vMsk != NULL)
+                    vMsk++;
+                continue;
+            }
+
+            Var += wDiff;
+            Sum += pDiff;
+
+            psF32 xWght = xDiff * pDiff;
+            psF32 yWght = yDiff * pDiff;
+
+            X1  += xWght;
+            Y1  += yWght;
+
+            XY  += xDiff * yWght;
+            X2  += xDiff * xWght;
+            Y2  += yDiff * yWght;
+
+            peakPixel = PS_MAX (*vPix, peakPixel);
+            numPixels++;
+            if (vMsk != NULL)
+                vMsk++;
+        }
+    }
+
+    // if we have less than (1/4) of the possible pixels, force a retry
+    // XXX EAM - the limit is a bit arbitrary.  make it user defined?
+    if ((numPixels < 0.75*R2) || (Sum <= 0)) {
+        psTrace (".psModules.pmSourceMoments", 3, "no valid pixels for source\n");
+        psTrace("psModules.objects", 3, "---- %s(false) end ----\n", __func__);
+        return (false);
+    }
+
+    psTrace (".psModules.pmSourceMoments", 5,
+             "sky: %f  Sum: %f  X1: %f  Y1: %f  X2: %f  Y2: %f  XY: %f  Npix: %d\n",
+             sky, Sum, X1, Y1, X2, Y2, XY, numPixels);
+
+    //
+    // first moment X  = X1/Sum + xc
+    // second moment X = sqrt (X2/Sum - (X1/Sum)^2)
+    // Sxy             = XY / Sum
+    //
+    x = X1/Sum;
+    y = Y1/Sum;
+    if ((fabs(x) > radius) || (fabs(y) > radius)) {
+        psTrace (".psModules.pmSourceMoments", 3,
+                 "large centroid swing; invalid peak %d, %d\n",
+                 source->peak->x, source->peak->y);
+        psTrace("psModules.objects", 3, "---- %s(false) end ----\n", __func__);
+        return (false);
+    }
+
+    source->moments->x = x + xPeak;
+    source->moments->y = y + yPeak;
+
+    // XXX EAM : Sxy needs to have x*y subtracted
+    source->moments->Sxy = XY/Sum - x*y;
+    source->moments->Sum = Sum;
+    source->moments->SN  = Sum / sqrt(Var);
+    source->moments->Peak = peakPixel;
+    source->moments->nPixels = numPixels;
+
+    // XXX EAM : these values can be negative, so we need to limit the range
+    source->moments->Sx = sqrt(PS_MAX(X2/Sum - PS_SQR(x), 0));
+    source->moments->Sy = sqrt(PS_MAX(Y2/Sum - PS_SQR(y), 0));
+
+    psTrace (".psModules.pmSourceMoments", 4,
+             "sky: %f  Sum: %f  x: %f  y: %f  Sx: %f  Sy: %f  Sxy: %f\n",
+             sky, Sum, source->moments->x, source->moments->y,
+             source->moments->Sx, source->moments->Sy, source->moments->Sxy);
+
+    psTrace("psModules.objects", 3, "---- %s(true) end ----\n", __func__);
+    return(true);
+}
+
+pmModel *pmSourceSelectModel (pmSource *source)
+{
+    switch (source->type) {
+    case PM_SOURCE_TYPE_STAR:
+        return source->modelPSF;
+
+    case PM_SOURCE_TYPE_EXTENDED:
+        return source->modelEXT;
+
+    default:
+        return NULL;
+    }
+    return NULL;
+}
Index: /tags/RHL_v1_1/psModules/src/objects/pmSource.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSource.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSource.h	(revision 9533)
@@ -0,0 +1,223 @@
+/** @file  pmSource.h
+ *
+ *  @author EAM, IfA; GLG, MHPCC
+ *
+ *  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-07-20 13:10:39 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+# ifndef PM_SOURCE_H
+# define PM_SOURCE_H
+
+/**
+ * In the object analysis process, we will use specific mask values to mark the
+ * image pixels. The following structure defines the relevant mask values.
+ *
+ * XXX: This is probably a bad solution: we will want to set mask values
+ * outside of the PSPHOT code.  Perhaps we can set up a registered set of mask
+ * values with specific meanings that other functions can add to or define?
+ 
+ * XXX We will only use the PM_MASK_xxx mask values
+typedef enum {
+    PM_SOURCE_MASK_CLEAR     = 0x00,
+    PM_SOURCE_MASK_INVALID   = 0x01,
+    PM_SOURCE_MASK_SATURATED = 0x02,
+    PM_SOURCE_MASK_MARKED    = 0x08,
+} psphotMaskValues;
+*/
+
+/** pmSourceType enumeration
+ *
+ * A given source may be identified as most-likely to be one of several source
+ * types. The pmSource entry pmSourceType defines the current best-guess for this
+ * source.
+ *
+ * XXX: The values given below are currently illustrative and will require
+ * some modification as the source classification code is developed. (TBD)
+ *
+ */
+typedef enum {
+    PM_SOURCE_TYPE_UNKNOWN,  ///< a cosmic-ray
+    PM_SOURCE_TYPE_DEFECT,  ///< a cosmic-ray
+    PM_SOURCE_TYPE_SATURATED,  ///< random saturated pixels
+    PM_SOURCE_TYPE_STAR,  ///< a good-quality star
+    PM_SOURCE_TYPE_EXTENDED,  ///< an extended object (eg, galaxy)
+} pmSourceType;
+
+typedef enum {
+    PM_SOURCE_MODE_DEFAULT    = 0x0000, ///<
+    PM_SOURCE_MODE_PSFMODEL   = 0x0001, ///<
+    PM_SOURCE_MODE_EXTMODEL   = 0x0002, ///<
+    PM_SOURCE_MODE_SUBTRACTED = 0x0004, ///<
+    PM_SOURCE_MODE_FITTED     = 0x0008, ///<
+    PM_SOURCE_MODE_FAIL       = 0x0010, ///<
+    PM_SOURCE_MODE_POOR       = 0x0020, ///<
+    PM_SOURCE_MODE_PAIR       = 0x0040, ///<
+    PM_SOURCE_MODE_PSFSTAR    = 0x0080, ///<
+    PM_SOURCE_MODE_SATSTAR    = 0x0100, ///<
+    PM_SOURCE_MODE_BLEND      = 0x0200, ///<
+    PM_SOURCE_MODE_LINEAR     = 0x0400, ///<
+    PM_SOURCE_MODE_TEMPSUB    = 0x0800, ///< XXX get me a better name!
+} pmSourceMode;
+
+/** pmSource data structure
+ *
+ *  This source has the capacity for several types of measurements. The
+ *  simplest measurement of a source is the location and flux of the peak pixel
+ *  associated with the source:
+ *
+ */
+typedef struct
+{
+    const int id;   ///< Unique ID for object
+    pmPeak *peak;   ///< Description of peak pixel.
+    psImage *pixels;   ///< Rectangular region including object pixels.
+    psImage *weight;   ///< Image variance.
+    psImage *mask;   ///< Mask which marks pixels associated with objects.
+    pmMoments *moments;   ///< Basic moments measure for the object.
+    pmModel *modelPSF;   ///< PSF Model fit (parameters and type)
+    pmModel *modelEXT;   ///< EXT (floating) Model fit (parameters and type).
+    pmSourceType type;   ///< Best identification of object.
+    pmSourceMode mode;   ///< Best identification of object.
+    psArray *blends;
+    float psfMag;   ///< calculated from flux in modelPsf
+    float extMag;   ///< calculated from flux in modelEXT
+    float errMag;   ///< error in psfMag OR extMag (depending on type)
+    float apMag;   ///< apMag corresponding to psfMag or extMag (depending on type)
+    float pixWeight;   // model-weighted coverage of valid pixels
+    psRegion region;   // area on image covered by selected pixels
+}
+pmSource;
+
+/** pmPSFClump data structure
+ *
+ * A collection of object moment measurements can be used to determine
+ * approximate object classes. The key to this analysis is the location and
+ * statistics (in the second-moment plane,
+ *
+ */
+typedef struct
+{
+    float X;
+    float dX;
+    float Y;
+    float dY;
+}
+pmPSFClump;
+
+
+/** pmSourceAlloc()
+ *
+ */
+pmSource  *pmSourceAlloc();
+
+// free just the pixels for a source, keeping derived data
+void pmSourceFreePixels(pmSource *source);
+
+bool pmIsSource(const psPtr ptr);
+
+/** pmSourceDefinePixels()
+ *
+ * Define psImage subarrays for the source located at coordinates x,y on the
+ * image set defined by readout. The pixels defined by this operation consist of
+ * a square window (of full width 2Radius+1) centered on the pixel which contains
+ * the given coordinate, in the frame of the readout. The window is defined to
+ * have limits which are valid within the boundary of the readout image, thus if
+ * the radius would fall outside the image pixels, the subimage is truncated to
+ * only consist of valid pixels. If readout->mask or readout->weight are not
+ * NULL, matching subimages are defined for those images as well. This function
+ * fails if no valid pixels can be defined (x or y less than Radius, for
+ * example). This function should be used to define a region of interest around a
+ * source, including both source and sky pixels.
+ *
+ */
+bool pmSourceDefinePixels(
+    pmSource *mySource,                 ///< source to be re-defined
+    const pmReadout *readout,  ///< base the source on this readout
+    psF32 x,                            ///< center coords of source
+    psF32 y,                            ///< center coords of source
+    psF32 Radius                        ///< size of box on source
+);
+
+bool pmSourceRedefinePixels (
+    pmSource *mySource,   ///< source to be re-defined
+    const pmReadout *readout,   ///< base the source on this readout
+    psF32 x,     ///< center coords of source
+    psF32 y,      ///< center coords of source
+    psF32 Radius   ///< size of box on source
+);
+
+/** pmSourcePSFClump()
+ *
+ * We use the source moments to make an initial, approximate source
+ * classification, and as part of the information needed to build a PSF model for
+ * the image. As long as the PSF shape does not vary excessively across the
+ * image, the sources which are represented by a PSF (the start) will have very
+ * similar second moments. The function pmSourcePSFClump searches a collection of
+ * sources with measured moments for a group with moments which are all very
+ * similar. The function returns a pmPSFClump structure, representing the
+ * centroid and size of the clump in the sigma_x, sigma_y second-moment plane.
+ *
+ * The goal is to identify and characterize the stellar clump within the
+ * sigma_x, sigma_y second-moment plane.  To do this, an image is constructed to
+ * represent this plane.  The units of sigma_x and sigma_y are in image pixels. A
+ * pixel in this analysis image represents 0.1 pixels in the input image. The
+ * dimensions of the image need only be 10 pixels. The peak pixel in this image
+ * (above a threshold of half of the image maximum) is found. The coordinates of
+ * this peak pixel represent the 2D mode of the sigma_x, sigma_y distribution.
+ * The sources with sigma_x, sigma_y within 0.2 pixels of this value are then
+ *  * used to calculate the median and standard deviation of the sigma_x, sigma_y
+ * values. These resulting values are returned via the pmPSFClump structure.
+ *
+ * The return value indicates the success (TRUE) of the operation.
+ *
+ * XXX: Limit the S/N of the candidate sources (part of Metadata)? (TBD).
+ * XXX: Save the clump parameters on the Metadata (TBD)
+ *
+ */
+pmPSFClump pmSourcePSFClump(
+    psArray *source,   ///< The input pmSource
+    psMetadata *metadata  ///< Contains classification parameters
+);
+
+
+/** pmSourceRoughClass()
+ *
+ * Based on the specified data values, make a guess at the source
+ * classification. The sources are provides as a psArray of pmSource entries.
+ * Definable parameters needed to make the classification are provided to the
+ * routine with the psMetadata structure. The rules (in SDRS) refer to values which
+ * can be extracted from the metadata using the given keywords. Except as noted,
+ * the data type for these parameters are psF32.
+ *
+ */
+bool pmSourceRoughClass(
+    psArray *source,   ///< The input pmSource
+    psMetadata *metadata,  ///< Contains classification parameters
+    pmPSFClump clump   ///< Statistics about the PSF clump
+);
+
+
+/** pmSourceMoments()
+ *
+ * Measure source moments for the given source, using the value of
+ * source.moments.sky provided as the local background value and the peak
+ * coordinates as the initial source location. The resulting moment values are
+ * applied to the source.moments entry, and the source is returned. The moments
+ * are measured within the given circular radius of the source.peak coordinates.
+ * The return value indicates the success (TRUE) of the operation.
+ *
+ */
+bool pmSourceMoments(
+    pmSource *source,   ///< The input pmSource for which moments will be computed
+    float radius   ///< Use a circle of pixels around the peak
+);
+
+
+// select the model used for this source
+pmModel *pmSourceSelectModel (pmSource *source);
+
+# endif /* PM_SOURCE_H */
Index: /tags/RHL_v1_1/psModules/src/objects/pmSourceContour.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSourceContour.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSourceContour.c	(revision 9533)
@@ -0,0 +1,455 @@
+/** @file  pmSourceContour.c
+ *
+ *  Functions to measure the local sky and sky variance for sources on images
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA: significant modifications.
+ *
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-15 09:49:01 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include "pslib.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourceContour.h"
+
+/******************************************************************************
+findValue(source, level, row, col, dir): a private function which determines
+the column coordinate of the model function which has the value "level".  If
+dir equals 0, then you loop leftwards from the peak pixel, otherwise,
+rightwards.
+ 
+XXX: reverse order of row,col args?
+ 
+XXX: Input row/col are in image coords.
+ 
+XXX: The result is returned in image coords.
+*****************************************************************************/
+# define LEFT false
+# define RIGHT true
+
+// return the first coordinate at or below the threshold in the requested direction
+static int findContourNeg(
+    psImage *image,
+    float threshold,
+    int x,
+    int y,
+    bool right)
+{
+
+    psTrace("psModules.objects", 4, "---- %s() begin ----\n", __func__);
+
+    // We define variables incr and lastColumn so that we can use the same loop
+    // whether we are stepping leftwards, or rightwards.
+
+    int incr;
+    int subCol;
+    int lastColumn;
+    if (right) {
+        incr = 1;
+        lastColumn = image->numCols - 1;
+    } else {
+        incr = -1;
+        lastColumn = 0;
+    }
+
+    subCol = x;
+
+    while (subCol != lastColumn) {
+        float value = image->data.F32[y][subCol];
+        if (value <= threshold) {
+            psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+            return (subCol);
+        }
+        subCol += incr;
+    }
+    psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+    return (lastColumn);
+}
+
+// return the last coordinate at or below the threshold in the requested direction
+static int findContourPos(
+    psImage *image,
+    float threshold,
+    int x,
+    int y,
+    bool right,
+    int xEnd)
+{
+
+    psTrace("psModules.objects", 4, "---- %s() begin ----\n", __func__);
+
+    // We define variables incr and lastColumn so that we can use the same loop
+    // whether we are stepping leftwards, or rightwards.
+
+    int incr;
+    int subCol;
+    int lastColumn;
+    if (right) {
+        incr = 1;
+        lastColumn = PS_MIN (image->numCols - 1, xEnd);
+    } else {
+        incr = -1;
+        lastColumn = PS_MAX (0, xEnd);
+    }
+
+    subCol = x;
+    while (subCol != lastColumn) {
+        float value = image->data.F32[y][subCol];
+        if (value >= threshold) {
+            psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+            if (subCol == x) {
+                return (subCol);
+            }
+            return (subCol);
+        }
+        subCol += incr;
+    }
+    psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+    return (lastColumn);
+}
+
+/******************************************************************************
+findValue(source, level, row, col, dir): a private function which determines
+the column coordinate of the model function which has the value "level".  If
+dir equals 0, then you loop leftwards from the peak pixel, otherwise,
+rightwards.
+ 
+XXX: reverse order of row,col args?
+ 
+XXX: Input row/col are in image coords.
+ 
+XXX: The result is returned in image coords.
+*****************************************************************************/
+static psF32 findValue(pmSource *source,
+                       psF32 level,
+                       psU32 row,
+                       psU32 col,
+                       psU32 dir)
+{
+    psTrace("psModules.objects", 4, "---- %s() begin ----\n", __func__);
+    //
+    // Convert coords to subImage space.
+    //
+    psU32 subRow = row - source->pixels->row0;
+    psU32 subCol = col - source->pixels->col0;
+
+    // Ensure that the starting column is allowable.
+    if (!((0 <= subCol) && (subCol < source->pixels->numCols))) {
+        psError(PS_ERR_UNKNOWN, true, "Starting column outside subImage range");
+        psTrace("psModules.objects", 4, "---- %s(NAN) end ----\n", __func__);
+        return(NAN);
+    }
+    if (!((0 <= subRow) && (subRow < source->pixels->numRows))) {
+        psTrace("psModules.objects", 4, "---- %s(NAN) end ----\n", __func__);
+        psError(PS_ERR_UNKNOWN, true, "Starting row outside subImage range");
+        return(NAN);
+    }
+
+    // XXX EAM : i changed this to match pmModelEval above, but see
+    // XXX EAM   the note below in pmSourceContour
+    psF32 oldValue = pmModelEval(source->modelEXT, source->pixels, subCol, subRow);
+    if (oldValue == level) {
+        psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+        return(((psF32) (subCol + source->pixels->col0)));
+    }
+
+    //
+    // We define variables incr and lastColumn so that we can use the same loop
+    // whether we are stepping leftwards, or rightwards.
+    //
+    psS32 incr;
+    psS32 lastColumn;
+    if (dir == 0) {
+        incr = -1;
+        lastColumn = -1;
+    } else {
+        incr = 1;
+        lastColumn = source->pixels->numCols;
+    }
+    subCol+=incr;
+
+    while (subCol != lastColumn) {
+        psF32 newValue = pmModelEval(source->modelEXT, source->pixels, subCol, subRow);
+        if (oldValue == level) {
+            psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+            return((psF32) (subCol + source->pixels->col0));
+        }
+
+        if ((newValue <= level) && (level <= oldValue)) {
+            // This is simple linear interpolation.
+            psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+            return( ((psF32) (subCol + source->pixels->col0)) + ((psF32) incr) * ((level - newValue) / (oldValue - newValue)) );
+        }
+
+        if ((oldValue <= level) && (level <= newValue)) {
+            // This is simple linear interpolation.
+            psTrace("psModules.objects", 4, "---- %s() end ----\n", __func__);
+            return( ((psF32) (subCol + source->pixels->col0)) + ((psF32) incr) * ((level - oldValue) / (newValue - oldValue)) );
+        }
+
+        subCol+=incr;
+    }
+
+    psTrace("psModules.objects", 4, "---- %s(NAN) end ----\n", __func__);
+    return(NAN);
+}
+
+/******************************************************************************
+new implementation of source contour function
+*****************************************************************************/
+psArray *pmSourceContour (psImage *image, int xc, int yc, float threshold)
+{
+
+    int xR, yR, x0, x1, x0s, x1s;
+
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(image, false);
+
+    int x = xc - image->col0;
+    int y = yc - image->row0;
+
+    // the requested point must be within the contour
+    if (image->data.F32[y][x] < threshold)
+        return NULL;
+
+    // Ensure that the starting column is allowable.
+    if (x < 0)
+        return NULL;
+    if (y < 0)
+        return NULL;
+    if (x >= image->numCols)
+        return NULL;
+    if (y >= image->numRows)
+        return NULL;
+
+    // Allocate data for x/y pairs.
+    psVector *xVec = psVectorAlloc(100, PS_TYPE_F32);
+    psVector *yVec = psVectorAlloc(100, PS_TYPE_F32);
+
+    // First row: find the left and right end-points
+    int Npt = 0;
+
+    x0 = findContourNeg (image, threshold, x, y, LEFT);
+    x1 = findContourNeg (image, threshold, x, y, RIGHT);
+    xVec->data.F32[Npt + 0] = image->col0 + x0;
+    xVec->data.F32[Npt + 1] = image->col0 + x1;
+    yVec->data.F32[Npt + 0] = image->row0 + y;
+    yVec->data.F32[Npt + 1] = image->row0 + y;
+    Npt += 2;
+
+    x0s = x0;
+    x1s = x1;
+
+    // look for contour outline above row
+    xR = x0s;
+    yR = y + 1;
+    while (yR < image->numRows) {
+        if (image->data.F32[yR][xR] < threshold) {
+            x0 = findContourPos (image, threshold, xR, yR, RIGHT, x1);
+            if (x0 == x1) {
+                // fprintf (stderr, "top: %d (%d - %d)\n", yR, xR, x1);
+                goto pt1;
+            }
+            x1 = findContourNeg (image, threshold, x0, yR, RIGHT);
+            x0--;
+        } else {
+            x0 = findContourNeg (image, threshold, xR, yR, LEFT);
+            x1 = findContourNeg (image, threshold, xR, yR, RIGHT);
+        }
+        // fprintf (stderr, "pos: %d (%d - %d)\n", yR, x0, x1);
+
+        xVec->data.F32[Npt + 0] = image->col0 + x0;
+        xVec->data.F32[Npt + 1] = image->col0 + x1;
+        yVec->data.F32[Npt + 0] = image->row0 + yR;
+        yVec->data.F32[Npt + 1] = image->row0 + yR;
+        Npt += 2;
+
+        if (Npt >= xVec->nalloc - 1) {
+            psVectorRealloc (xVec, xVec->nalloc + 100);
+            psVectorRealloc (yVec, yVec->nalloc + 100);
+        }
+        yR ++;
+        xR = x0;
+    }
+
+pt1:
+    // look for contour outline below row
+    xR = x0s;
+    x1 = x1s;
+    yR = y - 1;
+    while (yR >= 0) {
+        if (image->data.F32[yR][xR] < threshold) {
+            x0 = findContourPos (image, threshold, xR, yR, RIGHT, x1);
+            if (x0 == x1) {
+                // fprintf (stderr, "top: %d (%d - %d)\n", yR, xR, x1);
+                goto pt2;
+            }
+            x1 = findContourNeg (image, threshold, x0, yR, RIGHT);
+            x0--;
+        } else {
+            x0 = findContourNeg (image, threshold, xR, yR, LEFT);
+            x1 = findContourNeg (image, threshold, xR, yR, RIGHT);
+        }
+        // fprintf (stderr, "neg: %d (%d - %d)\n", yR, x0, x1);
+
+        xVec->data.F32[Npt + 0] = image->col0 + x0;
+        xVec->data.F32[Npt + 1] = image->col0 + x1;
+        yVec->data.F32[Npt + 0] = image->row0 + yR;
+        yVec->data.F32[Npt + 1] = image->row0 + yR;
+        Npt += 2;
+
+        if (Npt >= xVec->nalloc - 1) {
+            psVectorRealloc (xVec, xVec->nalloc + 100);
+            psVectorRealloc (yVec, yVec->nalloc + 100);
+        }
+        yR --;
+    }
+pt2:
+    xVec->n = Npt;
+    yVec->n = Npt;
+
+    // fprintf (stderr, "done\n");
+    psArray *tmpArray = psArrayAlloc(2);
+    tmpArray->n = 2;
+
+    tmpArray->data[0] = (psPtr *) xVec;
+    tmpArray->data[1] = (psPtr *) yVec;
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(tmpArray);
+}
+
+/******************************************************************************
+    pmSourceContour(src, img, level, mode): For an input subImage, and model, this
+    routine returns a psArray of coordinates that evaluate to the specified level.
+     
+    XXX: Probably should remove the "image" argument.
+    XXX: What type should the output coordinate vectors consist of?  col,row?
+    XXX: Why a pmArray output?
+    XXX: doex x,y correspond with col,row or row/col?
+    XXX: What is mode?
+    XXX: The top, bottom of the contour is not correctly determined.
+    XXX EAM : this function is using the model for the contour, but it should
+              be using only the image counts
+*****************************************************************************/
+psArray *pmSourceContour_Crude(pmSource *source,
+                               const psImage *image,
+                               psF32 level)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(image, false);
+    PS_ASSERT_PTR_NON_NULL(source->moments, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_PTR_NON_NULL(source->modelEXT, false);
+    // XXX EAM : what is the purpose of modelPSF/modelEXT?
+
+    //
+    // Allocate data for x/y pairs.
+    //
+    psVector *xVec = psVectorAlloc(2 * source->pixels->numRows, PS_TYPE_F32);
+    psVector *yVec = psVectorAlloc(2 * source->pixels->numRows, PS_TYPE_F32);
+    xVec->n = yVec->n = 2 * source->pixels->numRows;
+
+    //
+    // Start at the row with peak pixel, then decrement.
+    //
+    psS32 col = source->peak->x;
+    for (psS32 row = source->peak->y; row>= 0 ; row--) {
+        // XXX: yVec contain no real information.  Do we really need it?
+        yVec->data.F32[row] = (psF32) (source->pixels->row0 + row);
+        yVec->data.F32[row+yVec->n] = (psF32) (source->pixels->row0 + row);
+
+        // Starting at peak pixel, search leftwards for the column intercept.
+        psF32 leftIntercept = findValue(source, level, row, col, 0);
+        if (isnan(leftIntercept)) {
+            psError(PS_ERR_UNKNOWN, true, "Could not find contour edge (NAN)");
+            psFree(xVec);
+            psFree(yVec);
+            psTrace("psModules.objects", 3, "---- %s(NULL) end ----\n", __func__);
+            return(NULL);
+            //psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not find contour edge (NAN)\n");
+        }
+        xVec->data.F32[row] = ((psF32) source->pixels->col0) + leftIntercept;
+
+        // Starting at peak pixel, search rightwards for the column intercept.
+
+        psF32 rightIntercept = findValue(source, level, row, col, 1);
+        if (isnan(rightIntercept)) {
+            psError(PS_ERR_UNKNOWN, true, "Could not find contour edge (NAN)");
+            psFree(xVec);
+            psFree(yVec);
+            psTrace("psModules.objects", 3, "---- %s(NULL) end ----\n", __func__);
+            return(NULL);
+            //psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not find contour edge (NAN)\n");
+        }
+        psTrace("psModules.objects", 4, "The intercepts are (%.2f, %.2f)\n", leftIntercept, rightIntercept);
+        xVec->data.F32[row+xVec->n] = ((psF32) source->pixels->col0) + rightIntercept;
+
+        // Set starting column for next row
+        col = (psS32) ((leftIntercept + rightIntercept) / 2.0);
+    }
+    //
+    // Start at the row (+1) with peak pixel, then increment.
+    //
+    col = source->peak->x;
+    for (psS32 row = 1 + source->peak->y; row < source->pixels->numRows ; row++) {
+        // XXX: yVec contain no real information.  Do we really need it?
+        yVec->data.F32[row] = (psF32) (source->pixels->row0 + row);
+        yVec->data.F32[row+yVec->n] = (psF32) (source->pixels->row0 + row);
+
+        // Starting at peak pixel, search leftwards for the column intercept.
+        psF32 leftIntercept = findValue(source, level, row, col, 0);
+        if (isnan(leftIntercept)) {
+            psError(PS_ERR_UNKNOWN, true, "Could not find contour edge (NAN)");
+            psFree(xVec);
+            psFree(yVec);
+            psTrace("psModules.objects", 3, "---- %s(NULL) end ----\n", __func__);
+            return(NULL);
+            //psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not find contour edge (NAN)\n");
+        }
+        xVec->data.F32[row] = ((psF32) source->pixels->col0) + leftIntercept;
+
+        // Starting at peak pixel, search rightwards for the column intercept.
+        psF32 rightIntercept = findValue(source, level, row, col, 1);
+        if (isnan(rightIntercept)) {
+            psError(PS_ERR_UNKNOWN, true, "Could not find contour edge (NAN)");
+            psFree(xVec);
+            psFree(yVec);
+            psTrace("psModules.objects", 3, "---- %s(NULL) end ----\n", __func__);
+            return(NULL);
+            //psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not find contour edge (NAN)\n");
+        }
+        xVec->data.F32[row+xVec->n] = ((psF32) source->pixels->col0) + rightIntercept;
+
+        // Set starting column for next row
+        col = (psS32) ((leftIntercept + rightIntercept) / 2.0);
+    }
+
+    //
+    // Allocate an array for result, store coord vectors there.
+    //
+    psArray *tmpArray = psArrayAlloc(2);
+    tmpArray->n = 2;
+
+    tmpArray->data[0] = (psPtr *) yVec;
+    tmpArray->data[1] = (psPtr *) xVec;
+    psTrace("psModules.objects", 3, "---- %s() end ----\n", __func__);
+    return(tmpArray);
+}
Index: /tags/RHL_v1_1/psModules/src/objects/pmSourceContour.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSourceContour.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSourceContour.h	(revision 9533)
@@ -0,0 +1,37 @@
+/** @file  pmSourceContour.h
+ *
+ *  @author EAM, IfA; GLG, MHPCC
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:01:05 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+# ifndef PM_SOURCE_CONTOUR_H
+# define PM_SOURCE_CONTOUR_H
+
+psArray *pmSourceContour (psImage *image, int xc, int yc, float threshold);
+
+
+/** pmSourceContour()
+ *
+ * Find points in a contour for the given source at the given level. If type
+ * is PM_CONTOUR_CRUDE, the contour is found by starting at the source peak,
+ * running along each pixel row until the level is crossed, then interpolating to
+ * the level coordinate for that row. This is done for each row, with the
+ * starting point determined by the midpoint of the previous row, until the
+ * starting point has a value below the contour level. The returned contour
+ * consists of two vectors giving the x and y coordinates of the contour levels.
+ * This function may be used as part of the model guess inputs.  Other contour
+ * types may be specified in the future for more refined contours (TBD)
+ *
+ */
+psArray *pmSourceContour_Crude(
+    pmSource *source,   ///< The input pmSource
+    const psImage *image,  ///< The input image (float) (this arg should be removed)
+    float level   ///< The level of the contour
+);
+
+# endif /* PM_SOURCE_PHOTOMETRY_H */
Index: /tags/RHL_v1_1/psModules/src/objects/pmSourceFitModel.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSourceFitModel.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSourceFitModel.c	(revision 9533)
@@ -0,0 +1,596 @@
+/** @file  pmSourceFitModel.c
+ *
+ *  fit single source models to image pixels
+ *
+ *  @author EAM, IfA
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.11 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-10-13 02:29:14 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmModel.h"
+#include "pmPSF.h"
+#include "pmSource.h"
+#include "pmModelGroup.h"
+#include "pmSourceFitModel.h"
+
+// save as static values so they may be set externally
+static psF32 PM_SOURCE_FIT_MODEL_NUM_ITERATIONS = 15;
+static psF32 PM_SOURCE_FIT_MODEL_TOLERANCE = 0.1;
+static bool PM_PSF_POISSON_WEIGHTS = true;
+
+bool pmSourceFitModelInit (float nIter, float tol, bool poissonErrors)
+{
+
+    PM_SOURCE_FIT_MODEL_NUM_ITERATIONS = nIter;
+    PM_SOURCE_FIT_MODEL_TOLERANCE = tol;
+    PM_PSF_POISSON_WEIGHTS = poissonErrors;
+
+    return true;
+}
+
+bool pmSourceFitModel (pmSource *source,
+                       pmModel *model,
+                       pmSourceFitMode mode)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->moments, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_PTR_NON_NULL(source->mask, false);
+    PS_ASSERT_PTR_NON_NULL(source->weight, false);
+
+    // XXX EAM : is it necessary for the mask & weight to exist?  the
+    //           tests below could be conditions (!NULL)
+
+    psBool fitStatus = true;
+    psBool onPic     = true;
+    psBool rc        = true;
+
+    psVector *params = model->params;
+    psVector *dparams = model->dparams;
+    psVector *paramMask = NULL;
+
+    pmModelFunc modelFunc = pmModelFunc_GetFunction (model->type);
+
+    psF32 dSky = source->moments->dSky;
+
+    // maximum number of valid pixels
+    psS32 nPix = source->pixels->numRows * source->pixels->numCols;
+
+    // construct the coordinate and value entries
+    psArray *x = psArrayAlloc(nPix);
+    psVector *y = psVectorAlloc(nPix, PS_TYPE_F32);
+    psVector *yErr = psVectorAlloc(nPix, PS_TYPE_F32);
+
+    nPix = 0;
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+            // skip masked points
+            if (source->mask->data.U8[i][j]) {
+                continue;
+            }
+            // skip zero-weight points
+            if (source->weight->data.F32[i][j] == 0) {
+                continue;
+            }
+
+            psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+            coord->n = 2;
+
+            // Convert i/j to image space:
+            coord->data.F32[0] = (psF32) (j + source->pixels->col0);
+            coord->data.F32[1] = (psF32) (i + source->pixels->row0);
+            x->data[nPix] = (psPtr *) coord;
+            y->data.F32[nPix] = source->pixels->data.F32[i][j];
+            // psMinimizeLMChi2 takes wt = 1/dY^2
+            if (PM_PSF_POISSON_WEIGHTS) {
+                yErr->data.F32[nPix] = 1.0 / source->weight->data.F32[i][j];
+            } else {
+                yErr->data.F32[nPix] = 1.0 / dSky;
+            }
+            // XXX EAM : suggestion from RHL is to use the local sky as weight
+            // to avoid the bias from systematic errors
+            // here we would just use the source sky variance
+            nPix++;
+        }
+    }
+    x->n = nPix;
+    y->n = nPix;
+    yErr->n = nPix;
+
+    // XXX EAM : the new minimization API supplies the constraints as a struct
+    // XXX the chisq if a fcn of source flux. the minimization criterion should
+    // probably  be scaled somehow by flux.  measure the trend?  it depends on
+    // the about of systematic error in the models themselves...
+    psMinimization *myMin = psMinimizationAlloc(PM_SOURCE_FIT_MODEL_NUM_ITERATIONS,
+                            PM_SOURCE_FIT_MODEL_TOLERANCE);
+    psMinConstrain *constrain = psMinConstrainAlloc();
+
+    // set parameter mask based on fitting mode
+    paramMask = psVectorAlloc (params->n, PS_TYPE_U8);
+    paramMask->n = params->n;
+    psVectorInit (paramMask, 1);
+
+    int nParams = 0;
+    switch (mode) {
+    case PM_SOURCE_FIT_NORM:
+        // NORM-only model fits only source normalization (Io)
+        nParams = 1;
+        paramMask->data.U8[PM_PAR_I0] = 0;
+        break;
+    case PM_SOURCE_FIT_PSF:
+        // PSF model only fits x,y,Io
+        nParams = 3;
+        paramMask->data.U8[PM_PAR_I0] = 0;
+        paramMask->data.U8[PM_PAR_XPOS] = 0;
+        paramMask->data.U8[PM_PAR_YPOS] = 0;
+        break;
+    case PM_SOURCE_FIT_EXT:
+        // EXT model fits all params (except sky)
+        nParams = params->n - 1;
+        psVectorInit (paramMask, 0);
+        paramMask->data.U8[PM_PAR_SKY] = 1;
+        break;
+    case PM_SOURCE_FIT_PSF_AND_SKY:
+        nParams = 4;
+        psAbort ("pmSourceFitModel", "PSF + SKY not currently available");
+        break;
+    case PM_SOURCE_FIT_EXT_AND_SKY:
+        nParams = params->n;
+        psAbort ("pmSourceFitModel", "EXT + SKY not currently available");
+        break;
+    default:
+        psAbort ("pmSourceFitModel", "invalid fitting mode");
+    }
+    constrain->paramMask = paramMask;
+
+    if (nPix <  nParams + 1) {
+        psTrace (".pmObjects.pmSourceFitModel", 4, "insufficient valid pixels\n");
+        psTrace("psModules.objects", 3, "---- %s(false) end ----\n", __func__);
+        model->status = PM_MODEL_BADARGS;
+        psFree (x);
+        psFree (y);
+        psFree (yErr);
+        psFree (myMin);
+        psFree (constrain);
+        psFree (paramMask);
+        return(false);
+    }
+
+    // Set the parameter range checks
+    pmModelLimits modelLimits = pmModelLimits_GetFunction (model->type);
+    modelLimits (&constrain->paramDelta, &constrain->paramMin, &constrain->paramMax);
+
+    // force the floating parameters to fall within the contraint ranges
+    for (int i = 0; i < params->n; i++) {
+        if (constrain->paramMask->data.U8[i])
+            continue;
+        params->data.F32[i] = PS_MIN(constrain->paramMax->data.F32[i], params->data.F32[i]);
+        params->data.F32[i] = PS_MAX(constrain->paramMin->data.F32[i], params->data.F32[i]);
+    }
+
+    psImage *covar = psImageAlloc (params->n, params->n, PS_TYPE_F64);
+
+    psTrace (".pmObjects.pmSourceFitModel", 5, "fitting function\n");
+
+    fitStatus = psMinimizeLMChi2(myMin, covar, params, constrain, x, y, yErr, modelFunc);
+    for (int i = 0; i < dparams->n; i++) {
+        if ((paramMask != NULL) && paramMask->data.U8[i])
+            continue;
+        dparams->data.F32[i] = sqrt(covar->data.F64[i][i]);
+    }
+
+    // save the resulting chisq, nDOF, nIter
+    model->chisq = myMin->value;
+    model->nIter = myMin->iter;
+    model->nDOF  = y->n - nParams;
+
+    // get the Gauss-Newton distance for fixed model parameters
+    if (paramMask != NULL) {
+        psVector *delta = psVectorAlloc (params->n, PS_TYPE_F64);
+        delta->n = params->n;
+        psMinimizeGaussNewtonDelta(delta, params, NULL, x, y, yErr, modelFunc);
+        for (int i = 0; i < dparams->n; i++) {
+            if (!paramMask->data.U8[i])
+                continue;
+            dparams->data.F32[i] = delta->data.F64[i];
+        }
+        psFree (delta);
+    }
+
+    // set the model success or failure status
+    if (!fitStatus) {
+        model->status = PM_MODEL_NONCONVERGE;
+    } else {
+        model->status = PM_MODEL_SUCCESS;
+    }
+
+    // models can go insane: reject these
+    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) {
+        model->status = PM_MODEL_OFFIMAGE;
+    }
+
+    source->mode |= PM_SOURCE_MODE_FITTED;
+
+    psFree(x);
+    psFree(y);
+    psFree(yErr);
+    psFree(myMin);
+    psFree(covar);
+    psFree(constrain->paramMask);
+    psFree(constrain->paramMin);
+    psFree(constrain->paramMax);
+    psFree(constrain->paramDelta);
+    psFree(constrain);
+
+    rc = (onPic && fitStatus);
+    psTrace("psModules.objects", 3, "---- %s(%d) end ----\n", __func__, rc);
+    return(rc);
+}
+
+/********************* Source Model Set Functions ***************************/
+
+// these static variables are used by one pass of pmSourceFitSet to store
+// data for a model set.  If we are going to make psphot thread-safe, these
+// will have to go in a structure of their own.
+// sky, p1.1, p1.2, p1.3,... p1.n, p2.1, p2.2,
+// nPar = nSrc*(nOnePar - 1) + 1
+static pmModelFunc mFunc;
+static int nPar;
+static psVector *onePar;
+static psVector *oneDeriv;
+
+bool pmModelFitSetInit (pmModelType type)
+{
+
+    mFunc = pmModelFunc_GetFunction (type);
+    nPar  = pmModelParameterCount (type);
+
+    onePar = psVectorAlloc (nPar, PS_DATA_F32);
+    oneDeriv = psVectorAlloc (nPar, PS_DATA_F32);
+    onePar->n = oneDeriv->n = nPar;
+
+    return true;
+}
+
+void pmModelFitSetClear (void)
+{
+
+    psFree (onePar);
+    psFree (oneDeriv);
+    return;
+}
+
+psF32 pmModelFitSet(psVector *deriv,
+                    const psVector *params,
+                    const psVector *x)
+{
+
+    psF32 value;
+    psF32 model;
+
+    psF32 *PAR = onePar->data.F32;
+    psF32 *dPAR = oneDeriv->data.F32;
+
+    psF32 *pars = params->data.F32;
+    psF32 *dpars = (deriv == NULL) ? NULL : deriv->data.F32;
+
+    int nSrc = (params->n - 1) / (nPar - 1);
+
+    PAR[0] = model = pars[0];
+    for (int i = 0; i < nSrc; i++) {
+        int nOff = i*nPar - i;
+        for (int n = 1; n < nPar; n++) {
+            PAR[n] = pars[nOff + n];
+        }
+        if (deriv == NULL) {
+            value = mFunc (NULL, onePar, x);
+        } else {
+            value = mFunc (oneDeriv, onePar, x);
+            for (int n = 1; n < nPar; n++) {
+                dpars[nOff + n] = dPAR[n];
+            }
+        }
+        model += value;
+    }
+    if (deriv != NULL) {
+        dpars[0] = dPAR[0]*2.0;
+    }
+    return (model);
+}
+
+/*
+i:         0           1               2
+n:         1  2  3  4  5  6  1  2  3  4  5  6  1  2  3  4  5  6
+i*6 + n: 0 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18
+*/
+
+bool pmSourceFitSet (pmSource *source,
+                     psArray *modelSet,
+                     pmSourceFitMode mode)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->moments, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_PTR_NON_NULL(source->mask, false);
+    PS_ASSERT_PTR_NON_NULL(source->weight, false);
+
+    psBool fitStatus = true;
+    psBool onPic     = true;
+    psBool rc        = true;
+
+    // base values on first model
+    pmModel *model = modelSet->data[0];
+
+    // set the static variables
+    pmModelFitSetInit (model->type);
+
+    int nSrc = modelSet->n;
+    int nPar = model->params->n - 1;  // number of object parameters (excluding sky)
+
+    // define parameter vectors for source set
+    psVector *params = psVectorAlloc (nSrc*nPar + 1, PS_TYPE_F32);
+    psVector *dparams = psVectorAlloc (nSrc*nPar + 1, PS_TYPE_F32);
+    params->n = params->nalloc;
+    dparams->n = dparams->nalloc;
+
+    pmModelLimits modelLimits = pmModelLimits_GetFunction (model->type);
+
+    // define limits for a single-source model
+    psVector *oneDelta;
+    psVector *oneParMin;
+    psVector *oneParMax;
+    modelLimits (&oneDelta, &oneParMin, &oneParMax);
+
+    psMinConstrain *constrain = psMinConstrainAlloc();
+    constrain->paramMin = psVectorAlloc (nSrc*nPar + 1, PS_TYPE_F32);
+    constrain->paramMax = psVectorAlloc (nSrc*nPar + 1, PS_TYPE_F32);
+    constrain->paramMask = psVectorAlloc (nSrc*nPar + 1, PS_TYPE_U8);
+    constrain->paramDelta = psVectorAlloc (nSrc*nPar + 1, PS_TYPE_F32);
+    constrain->paramMin->n = constrain->paramMin->nalloc;
+    constrain->paramMax->n = constrain->paramMax->nalloc;
+    constrain->paramMask->n = constrain->paramMask->nalloc;
+    constrain->paramDelta->n = constrain->paramDelta->nalloc;
+
+    // set the parameter guesses and constraints for the multiple models
+    // first the values for the single sky parameter
+    params->data.F32[0] = model->params->data.F32[0];
+    constrain->paramMin->data.F32[0]   = oneParMin->data.F32[0];
+    constrain->paramMax->data.F32[0]   = oneParMax->data.F32[0];
+    constrain->paramDelta->data.F32[0] = oneDelta->data.F32[0];
+
+    // next, the values for the source parameters
+    for (int i = 0; i < nSrc; i++) {
+        model = modelSet->data[i];
+        for (int n = 1; n < nPar + 1; n++) {
+            params->data.F32[i*nPar + n] = model->params->data.F32[n];
+            constrain->paramDelta->data.F32[i*nPar + n] = oneDelta->data.F32[n];
+            constrain->paramMin->data.F32[i*nPar + n]   = oneParMin->data.F32[n];
+            constrain->paramMax->data.F32[i*nPar + n]   = oneParMax->data.F32[n];
+        }
+    }
+    psFree (oneDelta);
+    psFree (oneParMin);
+    psFree (oneParMax);
+
+    if (psTraceGetLevel(__func__) >= 5) {
+        for (int i = 0; i < params->n; i++) {
+            fprintf (stderr, "%d %f %d\n", i, params->data.F32[i], constrain->paramMask->data.U8[i]);
+        }
+    }
+
+    // set the parameter masks based on the fitting mode
+    int nParams = 0;
+    switch (mode) {
+    case PM_SOURCE_FIT_NORM:
+        // NORM-only model fits only source normalization (Io)
+        nParams = nSrc;
+        psVectorInit (constrain->paramMask, 1);
+        for (int i = 0; i < nSrc; i++) {
+            constrain->paramMask->data.U8[1 + i*nPar] = 0;
+        }
+        break;
+    case PM_SOURCE_FIT_PSF:
+        // PSF model only fits x,y,Io
+        nParams = 3*nSrc;
+        psVectorInit (constrain->paramMask, 1);
+        for (int i = 0; i < nSrc; i++) {
+            constrain->paramMask->data.U8[1 + i*nPar] = 0;
+            constrain->paramMask->data.U8[2 + i*nPar] = 0;
+            constrain->paramMask->data.U8[3 + i*nPar] = 0;
+        }
+        break;
+    case PM_SOURCE_FIT_EXT:
+        // EXT model fits all params (except sky)
+        nParams = nPar*nSrc;
+        psVectorInit (constrain->paramMask, 0);
+        constrain->paramMask->data.U8[0] = 1;
+        break;
+    case PM_SOURCE_FIT_PSF_AND_SKY:
+        nParams = 1 + 3*nSrc;
+        psAbort ("pmSourceFitModel", "PSF + SKY not currently available");
+        break;
+    case PM_SOURCE_FIT_EXT_AND_SKY:
+        nParams = 1 + nPar*nSrc;
+        psAbort ("pmSourceFitModel", "EXT + SKY not currently available");
+        break;
+    default:
+        psAbort ("pmSourceFitModel", "invalid fitting mode");
+    }
+
+    // force the floating parameters to fall within the contraint ranges
+    for (int i = 0; i < params->n; i++) {
+        if (constrain->paramMask->data.U8[i])
+            continue;
+        params->data.F32[i] = PS_MIN(constrain->paramMax->data.F32[i], params->data.F32[i]);
+        params->data.F32[i] = PS_MAX(constrain->paramMin->data.F32[i], params->data.F32[i]);
+    }
+
+    // maximum number of valid pixels
+    psS32 nPix = source->pixels->numRows * source->pixels->numCols;
+
+    // local sky variance
+    psF32 dSky = source->moments->dSky;
+
+    // construct the coordinate and value entries
+    psArray *x = psArrayAlloc(nPix);
+    psVector *y = psVectorAlloc(nPix, PS_TYPE_F32);
+    psVector *yErr = psVectorAlloc(nPix, PS_TYPE_F32);
+
+    nPix = 0;
+    for (psS32 i = 0; i < source->pixels->numRows; i++) {
+        for (psS32 j = 0; j < source->pixels->numCols; j++) {
+            // skip masked points
+            if (source->mask->data.U8[i][j]) {
+                continue;
+            }
+            // skip zero-weight points
+            if (source->weight->data.F32[i][j] == 0) {
+                continue;
+            }
+            psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+            coord->n = 2;
+
+            // Convert i/j to image space:
+            coord->data.F32[0] = (psF32) (j + source->pixels->col0);
+            coord->data.F32[1] = (psF32) (i + source->pixels->row0);
+            x->data[nPix] = (psPtr *) coord;
+            y->data.F32[nPix] = source->pixels->data.F32[i][j];
+            // psMinimizeLMChi2 takes wt = 1/dY^2
+            if (PM_PSF_POISSON_WEIGHTS) {
+                yErr->data.F32[nPix] = 1.0 / source->weight->data.F32[i][j];
+            } else {
+                yErr->data.F32[nPix] = 1.0 / dSky;
+            }
+            nPix++;
+        }
+    }
+    if (nPix <  nParams + 1) {
+        psTrace (__func__, 4, "insufficient valid pixels\n");
+        psTrace("psModules.objects", 3, "---- %s() end : fail pixels ----\n", __func__);
+        model->status = PM_MODEL_BADARGS;
+        psFree (x);
+        psFree (y);
+        psFree (yErr);
+        psFree (params);
+        psFree (dparams);
+        psFree(constrain->paramMask);
+        psFree(constrain->paramMin);
+        psFree(constrain->paramMax);
+        psFree(constrain->paramDelta);
+        psFree(constrain);
+        pmModelFitSetClear ();
+        return(false);
+    }
+    x->n = nPix;
+    y->n = nPix;
+    yErr->n = nPix;
+
+    psMinimization *myMin = psMinimizationAlloc(PM_SOURCE_FIT_MODEL_NUM_ITERATIONS,
+                            PM_SOURCE_FIT_MODEL_TOLERANCE);
+
+    psImage *covar = psImageAlloc (params->n, params->n, PS_TYPE_F64);
+
+    psTrace (__func__, 5, "fitting function\n");
+    fitStatus = psMinimizeLMChi2(myMin, covar, params, constrain, x, y, yErr, pmModelFitSet);
+    if (!fitStatus) {
+        // psError(PS_ERR_UNKNOWN, false, "Failed to fit model (%d)\n", nSrc);
+        psTrace("psModules.objects", 4, "Failed to fit model (%d)\n", nSrc);
+    }
+
+    // parameter errors from the covariance matrix
+    for (int i = 0; i < dparams->n; i++) {
+        if ((constrain->paramMask != NULL) && constrain->paramMask->data.U8[i])
+            continue;
+        dparams->data.F32[i] = sqrt(covar->data.F64[i][i]);
+    }
+
+    // get the Gauss-Newton distance for fixed model parameters
+    if (constrain->paramMask != NULL) {
+        psVector *delta = psVectorAlloc (params->n, PS_TYPE_F64);
+        delta->n = params->n;
+        psMinimizeGaussNewtonDelta(delta, params, NULL, x, y, yErr, pmModelFitSet);
+        for (int i = 0; i < dparams->n; i++) {
+            if (!constrain->paramMask->data.U8[i])
+                continue;
+            dparams->data.F32[i] = delta->data.F64[i];
+        }
+        psFree (delta);
+    }
+
+    // assign back the parameters to the models
+    for (int i = 0; i < nSrc; i++) {
+        model = modelSet->data[i];
+        model->params->data.F32[0] = params->data.F32[0];
+        for (int n = 1; n < nPar + 1; n++) {
+            model->params->data.F32[n] = params->data.F32[i*nPar + n];
+            model->dparams->data.F32[n] = dparams->data.F32[i*nPar + n];
+        }
+        // save the resulting chisq, nDOF, nIter
+        // these are not unique for any one source
+        model->chisq = myMin->value;
+        model->nIter = myMin->iter;
+        model->nDOF  = y->n - nParams;
+
+        // set the model success or failure status
+        model->status = fitStatus ? PM_MODEL_SUCCESS : PM_MODEL_NONCONVERGE;
+
+        // models can go insane: reject these
+        onPic &= (model->params->data.F32[2] >= source->pixels->col0);
+        onPic &= (model->params->data.F32[2] <  source->pixels->col0 + source->pixels->numCols);
+        onPic &= (model->params->data.F32[3] >= source->pixels->row0);
+        onPic &= (model->params->data.F32[3] <  source->pixels->row0 + source->pixels->numRows);
+        if (!onPic) {
+            model->status = PM_MODEL_OFFIMAGE;
+        }
+    }
+
+    source->mode |= PM_SOURCE_MODE_FITTED;
+
+    psFree(x);
+    psFree(y);
+    psFree(yErr);
+    psFree(myMin);
+    psFree(covar);
+    psFree(constrain->paramMask);
+    psFree(constrain->paramMin);
+    psFree(constrain->paramMax);
+    psFree(constrain->paramDelta);
+    psFree(constrain);
+    psFree(params);
+    psFree(dparams);
+
+    // free static memory used by pmModelFitSet
+    pmModelFitSetClear ();
+
+    rc = (onPic && fitStatus);
+    psTrace (__func__, 5, "onPic: %d, fitStatus: %d, nIter: %d, chisq: %f, nDof: %d\n", onPic, fitStatus, model->nIter, model->chisq, model->nDOF);
+    psTrace("psModules.objects", 3, "---- %s end : status %d ----\n", __func__, rc);
+    return(rc);
+}
+
Index: /tags/RHL_v1_1/psModules/src/objects/pmSourceFitModel.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSourceFitModel.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSourceFitModel.h	(revision 9533)
@@ -0,0 +1,69 @@
+/** @file  pmSourceFitModel.h
+ *
+ *  @author EAM, IfA; GLG, MHPCC
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:01:05 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+# ifndef PM_SOURCE_FIT_MODEL_H
+# define PM_SOURCE_FIT_MODEL_H
+
+typedef enum {
+    PM_SOURCE_FIT_NORM,
+    PM_SOURCE_FIT_PSF,
+    PM_SOURCE_FIT_EXT,
+    PM_SOURCE_FIT_PSF_AND_SKY,
+    PM_SOURCE_FIT_EXT_AND_SKY
+} pmSourceFitMode;
+
+bool pmSourceFitModelInit(
+    float nIter,   ///< max number of allowed iterations
+    float tol,      ///< convergence criterion
+    bool poissonErrors   // use poisson errors for fits?
+);
+
+/** pmSourceFitModel()
+ *
+ * Fit the requested model to the specified source. The starting guess for the model is given
+ * by the input source.model parameter values. The pixels of interest are specified by the
+ * source.pixels and source.mask entries. This function calls psMinimizeLMChi2() on the image
+ * data. The function returns TRUE on success or FALSE on failure.
+ *
+ */
+bool pmSourceFitModel(
+    pmSource *source,   ///< The input pmSource
+    pmModel *model,   ///< model to be fitted
+    pmSourceFitMode mode  ///< define parameters to be fitted
+);
+
+
+// initialize data for a group of object models
+bool pmModelFitSetInit (pmModelType type);
+
+// clear data for a group of object models
+void pmModelFitSetClear (void);
+
+// function used to fit a group of object models
+psF32 pmModelFitSet(psVector *deriv,
+                    const psVector *params,
+                    const psVector *x);
+
+/** pmSourceFitSet()
+ *
+ * Fit the requested model to the specified source. The starting guess for the model is given
+ * by the input source.model parameter values. The pixels of interest are specified by the
+ * source.pixels and source.mask entries. This function calls psMinimizeLMChi2() on the image
+ * data. The function returns TRUE on success or FALSE on failure.
+ *
+ */
+bool pmSourceFitSet(
+    pmSource *source,   ///< The input pmSource
+    psArray *modelSet,   ///< model to be fitted
+    pmSourceFitMode mode  ///< define parameters to be fitted
+);
+
+# endif /* PM_SOURCE_FIT_MODEL_H */
Index: /tags/RHL_v1_1/psModules/src/objects/pmSourceIO.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSourceIO.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSourceIO.c	(revision 9533)
@@ -0,0 +1,460 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.12 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-10-07 03:54:29 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmModel.h"
+#include "pmPSF.h"
+#include "pmSource.h"
+#include "pmModelGroup.h"
+#include "pmSourceIO.h"
+
+// translations between psphot object types and dophot object types
+int pmSourceDophotType (pmSource *source)
+{
+    switch (source->type) {
+
+    case PM_SOURCE_TYPE_DEFECT:
+    case PM_SOURCE_TYPE_SATURATED:
+        return (8);
+
+    case PM_SOURCE_TYPE_STAR:
+        if (source->mode & PM_SOURCE_MODE_SATSTAR)
+            return (10);
+        if (source->mode & PM_SOURCE_MODE_POOR)
+            return (7);
+        if (source->mode & PM_SOURCE_MODE_FAIL)
+            return (4);
+        return (1);
+
+    case PM_SOURCE_TYPE_EXTENDED:
+        return (2);
+
+    default:
+        return (0);
+    }
+    return (0);
+}
+
+// Given a FITS file pointer, write the table of object data
+bool pmFPAviewWriteObjects (const pmFPAview *view, pmFPAfile *file)
+{
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        if (!pmFPAWriteObjects (fpa, view, file)) {
+            psError(PS_ERR_IO, false, "Failed to write objects from fpa");
+            return false;
+        }
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        psError(PS_ERR_UNKNOWN, false, "Writing chip == %d (>= chips->n == %ld)", view->chip, fpa->chips->n);
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        if (!pmChipWriteObjects (chip, view, file)) {
+            psError(PS_ERR_IO, false, "Failed to write objects from chip");
+            return false;
+        }
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        psError(PS_ERR_UNKNOWN, false, "Writing cell == %d (>= cells->n == %ld)",
+                view->cell, chip->cells->n);
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        if (!pmCellWriteObjects (cell, view, file)) {
+            psError(PS_ERR_IO, false, "Failed to write objects from cell");
+            return false;
+        }
+
+        return true;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        psError(PS_ERR_UNKNOWN, false, "Writing readout == %d (>= readouts->n == %ld)",
+                view->readout, cell->readouts->n);
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    if (!pmReadoutWriteObjects (readout, view, file)) {
+        psError(PS_ERR_IO, false, "Failed to write objects from readout %d", view->readout);
+        return false;
+    }
+
+    return true;
+}
+
+// read in all chip-level Objects files for this FPA
+bool pmFPAWriteObjects (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file)
+{
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+
+        pmChip *chip = fpa->chips->data[i];
+        if (!pmChipWriteObjects (chip, view, file)) {
+            psError(PS_ERR_IO, false, "Failed to write %dth chip", i);
+            return false;
+        }
+    }
+    return true;
+}
+
+// read in all cell-level Objects files for this chip
+bool pmChipWriteObjects (pmChip *chip, const pmFPAview *view, pmFPAfile *file)
+{
+
+    for (int i = 0; i < chip->cells->n; i++) {
+
+        pmCell *cell = chip->cells->data[i];
+        if (!pmCellWriteObjects (cell, view, file)) {
+            psError(PS_ERR_IO, false, "Failed to write %dth cell", i);
+            return false;
+        }
+    }
+    return true;
+}
+
+// read in all readout-level Objects files for this cell
+bool pmCellWriteObjects (pmCell *cell, const pmFPAview *view, pmFPAfile *file)
+{
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+
+        pmReadout *readout = cell->readouts->data[i];
+        if (!pmReadoutWriteObjects (readout, view, file)) {
+            psError(PS_ERR_IO, false, "Failed to write %dth readout", i);
+            return false;
+        }
+    }
+    return true;
+}
+
+// read in all readout-level Objects files for this cell
+bool pmReadoutWriteObjects (pmReadout *readout, const pmFPAview *view, pmFPAfile *file)
+{
+
+    bool status;
+    char *filename;
+    char *dataname;
+    char *headname;
+    pmHDU *hdu;
+    pmHDU *phu;
+    psMetadata *updates;
+    psMetadata *outhead;
+
+    psArray *sources = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.SOURCES");
+    if (sources == NULL)
+        return false;
+
+    switch (file->type) {
+    case PM_FPA_FILE_RAW:
+        filename = pmFPAfileNameFromRule (file->filerule, file, view);
+        pmSourcesWriteRAW (sources, filename);
+        psFree (filename);
+        break;
+
+    case PM_FPA_FILE_OBJ:
+        filename = pmFPAfileNameFromRule (file->filerule, file, view);
+        pmSourcesWriteOBJ (sources, filename);
+        psFree (filename);
+        break;
+
+    case PM_FPA_FILE_SX:
+        filename = pmFPAfileNameFromRule (file->filerule, file, view);
+        pmSourcesWriteSX (sources, filename);
+        psFree (filename);
+        break;
+
+    case PM_FPA_FILE_CMP:
+        // a SPLIT format : only one header and object table per file
+        hdu = pmFPAviewThisHDU (view, file->fpa);
+        filename = pmFPAfileNameFromRule (file->filerule, file, view);
+
+        // copy the header to an output header, add the output header data
+        outhead = psMetadataCopy (NULL, hdu->header);
+
+        // copy over the entries saved in the
+        updates = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.HEADER");
+        if (updates) {
+            psMetadataCopy (outhead, updates);
+            psFree (updates);
+        }
+
+        updates = psMetadataLookupPtr (&status, readout->analysis, "PSASTRO.HEADER");
+        if (updates) {
+            psMetadataCopy (outhead, updates);
+            psFree (updates);
+        }
+
+        bool status = pmSourcesWriteCMP (sources, filename, outhead);
+        psFree (outhead);
+        psFree (filename);
+
+        if (!status) {
+            psError(PS_ERR_IO, false, "Failed to write CMP file\n");
+            return false;
+        }
+        break;
+
+    case PM_FPA_FILE_CMF:
+        // write a PHU? (only if input image is MEF)
+        // write a header? (only if this is the first readout for cell)
+        //   note that the file->header is set to track the last hdu->header written
+        // write the data? (always?)
+
+        // get the current header
+        hdu = pmFPAviewThisHDU (view, file->fpa);
+
+        // if file does not yet have a PHU, attempt to write one to disk
+        if (file->phu == NULL) {
+            // get the corresponding phu
+            phu = pmFPAviewThisPHU (view, file->fpa);
+
+            // if there is no PHU, this is a single header+image (extension-less) file
+            // if there is a PHU, write it out as a 'blank'
+            if (phu) {
+                outhead = psMetadataCopy (NULL, phu->header);
+                psMetadataAddBool (outhead, PS_LIST_TAIL, "EXTEND", PS_META_REPLACE, "this file has extensions", true);
+                // XXX what is the EXTNAME??
+                psFitsWriteBlank (file->fits, outhead, "");
+                file->phu = phu->header;
+                psTrace ("pmFPAfile", 5, "wrote phu %s (type: %d)\n", file->filename, file->type);
+                psFree (outhead);
+            }
+        }
+
+        // this header block is new, write it to disk
+        if (hdu->header != file->header) {
+            // determine name for header extension
+            headname = pmFPAfileNameFromRule (file->extxtra, file, view);
+            outhead = psMetadataCopy (NULL, hdu->header);
+            psMetadataAddStr (outhead, PS_LIST_TAIL, "EXTNAME", PS_META_REPLACE, "extension name", headname);
+            if (file->phu == NULL) {
+                // this hdu->header acts as the PHU: set EXTEND to be true
+                psMetadataAddBool (outhead, PS_LIST_TAIL, "EXTEND", PS_META_REPLACE, "this file has extensions", true);
+            }
+
+            // XXX : see note below about DVO/addstar expectations
+            updates = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.HEADER");
+            psMetadataCopy (outhead, updates);
+
+            psFitsWriteBlank (file->fits, outhead, headname);
+            psTrace ("pmFPAfile", 5, "wrote ext head %s (type: %d)\n", file->filename, file->type);
+            file->header = hdu->header;
+            psFree (outhead);
+            psFree (headname);
+        }
+
+        // write this table to disk
+        dataname = pmFPAfileNameFromRule (file->extrule, file, view);
+
+        // XXX : currently, this is being added to the TABLE header
+        //       this may be the correct location for a MEF file, but it is
+        //       inconsistent with DVO/addstar expectations.  DVO/addstar
+        //       currently only expects a single header/table pair, and the
+        //       metadata are in the blank (image) header, not the table header
+        //       it may be necessary to put the results from the first readout
+        //       in the blank image header to cover this case.
+        updates = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.HEADER");
+        pmSourcesWriteCMF (file->fits, sources, updates, dataname);
+        psTrace ("pmFPAfile", 5, "wrote ext data %s (type: %d)\n", file->filename, file->type);
+        psFree (dataname);
+        break;
+
+    default:
+        fprintf (stderr, "warning: type mismatch\n");
+        break;
+    }
+    return true;
+}
+// a MEF CMF file has: PHU, CELL-HEAD, TABLE, CELL-HEAD, TABLE, TABLE, TABLE...
+
+// Given a FITS file pointer, read the table of object data
+bool pmFPAviewReadObjects (const pmFPAview *view, pmFPAfile *file)
+{
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        pmFPAReadObjects (fpa, view, file);
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        pmChipReadObjects (chip, view, file);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        pmCellReadObjects (cell, view, file);
+        return true;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    pmReadoutReadObjects (readout, view, file);
+    return true;
+}
+
+// read in all chip-level Objects files for this FPA
+bool pmFPAReadObjects (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file)
+{
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        pmChipReadObjects (chip, view, file);
+    }
+    return true;
+}
+
+// read in all cell-level Objects files for this chip
+bool pmChipReadObjects (pmChip *chip, const pmFPAview *view, pmFPAfile *file)
+{
+    for (int i = 0; i < chip->cells->n; i++) {
+        pmCell *cell = chip->cells->data[i];
+        pmCellReadObjects (cell, view, file);
+    }
+    return true;
+}
+
+// read in all readout-level Objects files for this cell
+bool pmCellReadObjects (pmCell *cell, const pmFPAview *view, pmFPAfile *file)
+{
+    // determine the number of readouts:
+    switch (file->type) {
+    case PM_FPA_FILE_CMP: {
+            pmReadout *readout;
+            readout = pmReadoutAlloc (cell);
+            psFree (readout);
+            break;
+        }
+    default:
+        psAbort ("pmSourceIO", "n readouts is not yet defined\n");
+    }
+    for (int i = 0; i < cell->readouts->n; i++) {
+        pmReadout *readout = cell->readouts->data[i];
+        pmReadoutReadObjects (readout, view, file);
+        cell->data_exists = true;
+    }
+    return true;
+}
+
+// read in all readout-level Objects files for this cell
+bool pmReadoutReadObjects (pmReadout *readout, const pmFPAview *view, pmFPAfile *file)
+{
+
+    bool status;
+    psArray *sources = NULL;
+    pmHDU *hdu;
+
+    switch (file->type) {
+    case PM_FPA_FILE_OBJ:
+        fprintf (stderr, "warning: OBJ is not supported as an input object format\n");
+        break;
+
+    case PM_FPA_FILE_SX:
+        fprintf (stderr, "warning: SX is not supported as an input object format\n");
+        break;
+
+    case PM_FPA_FILE_CMP:
+        // a SPLIT format : only one header and object table per file
+
+        // read in header, if not yet loaded
+        hdu = pmFPAviewThisHDU (view, file->fpa);
+        if (file->filename)
+            psFree (file->filename);
+        file->filename = pmFPAfileNameFromRule (file->filerule, file, view);
+
+        // indirect filenames
+        if (!strcasecmp (file->filename, "@FILES")) {
+            psFree (file->filename);
+            char *extra = pmFPAfileNameFromRule (file->filextra, file, view);
+            file->filename = psMetadataLookupStr (&status, file->names, extra);
+            psFree (extra);
+            if (file->filename == NULL)
+                return false;
+            // psMetadataLookupStr just returns a view, file->filename must be protected
+            psMemIncrRefCounter (file->filename);
+        }
+        file->fits = psFitsOpen (file->filename, "r");
+        if (hdu->header != NULL) {
+            psFree (hdu->header);
+        }
+        hdu->header = psFitsReadHeader (NULL, file->fits);
+        psFitsClose (file->fits);
+        file->fits = NULL;
+
+        sources = pmSourcesReadCMP (file->filename, hdu->header);
+        break;
+
+    case PM_FPA_FILE_CMF:
+        // read in header, if not yet loaded
+        hdu = pmFPAviewThisHDU (view, file->fpa);
+        if (hdu->header == NULL) {
+            char *headname = pmFPAfileNameFromRule (file->extxtra, file, view);
+            psFitsMoveExtName (file->fits, headname);
+            hdu->header = psFitsReadHeader (NULL, file->fits);
+            psFree (headname);
+        }
+
+        char *dataname = pmFPAfileNameFromRule (file->extrule, file, view);
+        psFitsMoveExtName (file->fits, dataname);
+        sources = pmSourcesReadCMF (file->fits, hdu->header);
+        psFree (dataname);
+        break;
+
+    default:
+        fprintf (stderr, "warning: type mismatch\n");
+        break;
+    }
+    readout->data_exists = true;
+    status = psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES", PS_DATA_ARRAY, "psphot sources", sources);
+    psFree (sources);
+    return true;
+}
+
+
Index: /tags/RHL_v1_1/psModules/src/objects/pmSourceIO.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSourceIO.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSourceIO.h	(revision 9533)
@@ -0,0 +1,44 @@
+/** @file  pmSourceIO.h
+ *
+ *  @author EAM, IfA; GLG, MHPCC
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:01:05 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+# ifndef PM_SOURCE_IO_H
+# define PM_SOURCE_IO_H
+
+int pmSourceDophotType (pmSource *source);
+
+bool pmSourcesWriteRAW (psArray *sources, char *filename);
+bool pmSourcesWriteOBJ (psArray *sources, char *filename);
+bool pmSourcesWriteSX (psArray *sources, char *filename);
+bool pmSourcesWriteCMP (psArray *sources, char *filename, psMetadata *header);
+bool pmSourcesWriteCMF (psFits *fits, psArray *sources, psMetadata *header, char *extname);
+
+psArray *pmSourcesReadCMP (char *filename, psMetadata *header);
+psArray *pmSourcesReadCMF (psFits *fits, psMetadata *header);
+
+bool pmSourcesWritePSFs (psArray *sources, char *filename);
+bool pmSourcesWriteEXTs (psArray *sources, char *filename);
+bool pmSourcesWriteNULLs (psArray *sources, char *filename);
+bool pmMomentsWriteText (psArray *sources, char *filename);
+bool pmPeaksWriteText (psArray *peaks, char *filename);
+
+bool pmFPAviewReadObjects (const pmFPAview *view, pmFPAfile *file);
+bool pmFPAReadObjects (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file);
+bool pmChipReadObjects (pmChip *chip, const pmFPAview *view, pmFPAfile *file);
+bool pmCellReadObjects (pmCell *cell, const pmFPAview *view, pmFPAfile *file);
+bool pmReadoutReadObjects (pmReadout *readout, const pmFPAview *view, pmFPAfile *file);
+
+bool pmFPAviewWriteObjects (const pmFPAview *view, pmFPAfile *file);
+bool pmFPAWriteObjects (pmFPA *fpa, const pmFPAview *view, pmFPAfile *file);
+bool pmChipWriteObjects (pmChip *chip, const pmFPAview *view, pmFPAfile *file);
+bool pmCellWriteObjects (pmCell *cell, const pmFPAview *view, pmFPAfile *file);
+bool pmReadoutWriteObjects (pmReadout *readout, const pmFPAview *view, pmFPAfile *file);
+
+# endif /* PM_SOURCE_IO_H */
Index: /tags/RHL_v1_1/psModules/src/objects/pmSourceIO_CMF.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSourceIO_CMF.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSourceIO_CMF.c	(revision 9533)
@@ -0,0 +1,180 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-10-13 02:23:55 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmModel.h"
+#include "pmPSF.h"
+#include "pmSource.h"
+#include "pmModelGroup.h"
+#include "pmSourceIO.h"
+
+// elixir-style FITS table output (header + table in 1st extension)
+// this format consists of a header derived from the image header
+// followed by a zero-size matrix, followed by the table data
+bool pmSourcesWriteCMF (psFits *fits, psArray *sources, psMetadata *header, char *extname)
+{
+
+    psArray *table;
+    psMetadata *row;
+    int i, type;
+    psF32 *PAR, *dPAR;
+    float lsky;
+    bool status;
+    psEllipseShape shape;
+    psEllipseAxes axes;
+
+    // find config information for output header
+    float ZERO_POINT = psMetadataLookupF32 (&status, header, "ZERO_PT");
+
+    table = psArrayAlloc (sources->n);
+    table->n = 0;
+
+    for (i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+        pmModel *model = pmSourceSelectModel (source);
+        if (model == NULL)
+            continue;
+
+        PAR = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        type = pmSourceDophotType (source);
+        #if 1
+
+        float sky = PAR[PM_PAR_SKY];
+        #else  // we eventually need to interpolate here to get the sky background. XXX
+
+        file = psMetadataLookupPtr(&status, header, "PSPHOT.BACKGND");
+        if (file != NULL) {
+            background = pmFPAviewThisReadout (view, file->fpa);
+        }
+        float sky = source->moments->Sky + 0; // etc.
+        #endif
+
+        lsky = (sky < 1.0) ? 0.0 : log10(sky);
+
+        shape.sx  = PAR[PM_PAR_SXX];
+        shape.sy  = PAR[PM_PAR_SYY];
+        shape.sxy = PAR[PM_PAR_SXY];
+        axes = psEllipseShapeToAxes (shape);
+
+        row = psMetadataAlloc ();
+        psMetadataAdd (row, PS_LIST_TAIL, "X_PIX",   PS_DATA_F32, "", PAR[PM_PAR_XPOS]);
+        psMetadataAdd (row, PS_LIST_TAIL, "Y_PIX",   PS_DATA_F32, "", PAR[PM_PAR_YPOS]);
+        psMetadataAdd (row, PS_LIST_TAIL, "MAG_RAW", PS_DATA_F32, "", PS_MIN (99.0, source->psfMag + ZERO_POINT));
+        psMetadataAdd (row, PS_LIST_TAIL, "MAG_GAL", PS_DATA_F32, "", PS_MIN (99.0, source->extMag + ZERO_POINT));
+        psMetadataAdd (row, PS_LIST_TAIL, "MAG_AP",  PS_DATA_F32, "", PS_MIN (99.0, source->apMag + ZERO_POINT));
+        psMetadataAdd (row, PS_LIST_TAIL, "MAG_ERR", PS_DATA_F32, "", PS_MIN (999, 1000*source->errMag));
+        psMetadataAdd (row, PS_LIST_TAIL, "LOG_SKY", PS_DATA_F32, "", lsky);
+        psMetadataAdd (row, PS_LIST_TAIL, "FWHM_X",  PS_DATA_F32, "", axes.major);
+        psMetadataAdd (row, PS_LIST_TAIL, "FWHM_Y",  PS_DATA_F32, "", axes.minor);
+        psMetadataAdd (row, PS_LIST_TAIL, "THETA",   PS_DATA_F32, "", axes.theta);
+        psMetadataAdd (row, PS_LIST_TAIL, "DOPHOT",  PS_DATA_U8,  "", type);
+        psMetadataAdd (row, PS_LIST_TAIL, "WEIGHT",  PS_DATA_U8,  "", PS_MIN (255, PS_MAX(0, 255*source->pixWeight)));
+
+        psArrayAdd (table, 100, row);
+        psFree (row);
+    }
+
+    // create the basic table header
+    psMetadataAdd (header, PS_LIST_HEAD, "EXTNAME", PS_DATA_STRING | PS_META_REPLACE, "extension name", extname);
+
+    // XXX this is broken : does not write the existing header elements except EXTNAME
+    psTrace ("pmFPAfile", 5, "writing ext data %s\n", extname);
+    if (!psFitsWriteTable (fits, header, table, extname)) {
+        psError(PS_ERR_IO, false, "writing ext data %s\n", extname);
+        psFree(table);
+        return false;
+    }
+
+    psFree (table);
+    return true;
+}
+
+// read in a readout from the fits file
+psArray *pmSourcesReadCMF (psFits *fits, psMetadata *header)
+{
+
+    bool status;
+    psF32 *PAR, *dPAR;
+    psEllipseShape shape;
+    psEllipseAxes axes;
+    float lsky;
+
+    // define PSF model type
+    int modelType = pmModelSetType ("PS_MODEL_GAUSS");
+
+    char *PSF_NAME = psMetadataLookupStr (&status, header, "PSF_NAME");
+    if (PSF_NAME != NULL) {
+        modelType = pmModelSetType (PSF_NAME);
+    }
+
+    // find config information for output header
+    float ZERO_POINT = psMetadataLookupF32 (&status, header, "ZERO_PT");
+    if (!status)
+        ZERO_POINT = 25.0;
+
+    psArray *table = psFitsReadTable (fits);
+    // validate a single row of the table (must match SMP)
+
+    psArray *sources = psArrayAlloc (table->n);
+    sources->n = table->n;
+
+    // convert the table to the pmSource entries
+    // XXX need to chooose PSF vs EXT, based on type?
+    for (int i = 0; i < table->n; i++) {
+        pmSource *source = pmSourceAlloc ();
+        pmModel *model = pmModelAlloc (modelType);
+        source->modelPSF  = model;
+
+        PAR = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        psMetadata *row = table->data[i];
+
+        lsky             = psMetadataLookupF32 (&status, row, "LOG_SKY");
+        PAR[PM_PAR_SKY]  = pow(lsky, 10.0);
+        PAR[PM_PAR_XPOS] = psMetadataLookupF32 (&status, row, "X_PIX");
+        PAR[PM_PAR_YPOS] = psMetadataLookupF32 (&status, row, "Y_PIX");
+        axes.major       = psMetadataLookupF32 (&status, row, "FWHM_X");
+        axes.minor       = psMetadataLookupF32 (&status, row, "FWHM_Y");
+        axes.theta       = psMetadataLookupF32 (&status, row, "THETA");
+
+        shape = psEllipseAxesToShape (axes);
+
+        PAR[PM_PAR_SXX] = shape.sx;
+        PAR[PM_PAR_SYY] = shape.sy;
+        PAR[PM_PAR_SXY] = shape.sxy;
+
+        source->psfMag = psMetadataLookupF32 (&status, row, "MAG_RAW") - ZERO_POINT;
+        source->extMag = psMetadataLookupF32 (&status, row, "MAG_GAL") - ZERO_POINT;
+        source->errMag = psMetadataLookupF32 (&status, row, "MAG_ERR");
+        source->apMag  = psMetadataLookupF32 (&status, row, "MAG_AP")  - ZERO_POINT;
+
+        sources->data[i] = source;
+    }
+    return (sources);
+}
Index: /tags/RHL_v1_1/psModules/src/objects/pmSourceIO_CMP.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSourceIO_CMP.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSourceIO_CMP.c	(revision 9533)
@@ -0,0 +1,292 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.15 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-30 02:29:59 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmModel.h"
+#include "pmPSF.h"
+#include "pmSource.h"
+#include "pmModelGroup.h"
+#include "pmSourceIO.h"
+
+// XXX make sure in and out have consistent zero-point adjustments
+// XXX make sure the angle in correctly translated to/from degrees
+// XXX we lose all information from the 'type' field
+
+// elixir-style pseudo FITS table (header + ascii list)
+bool pmSourcesWriteCMP (psArray *sources, char *filename, psMetadata *header)
+{
+
+    int i, type, nSrc;
+    // psMetadataItem *mdi;
+    psF32 *PAR, *dPAR;
+    float lsky = 0;
+    bool status;
+    psEllipseShape shape;
+    psEllipseAxes axes;
+
+    // find config information for output header
+    float ZERO_POINT = psMetadataLookupF32 (&status, header, "ZERO_PT");
+    if (!status)
+        ZERO_POINT = 25.0;
+
+    // count the number of sources which will be written
+    for (i = nSrc = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+        pmModel *model = pmSourceSelectModel (source);
+        if (model == NULL)
+            continue;
+        nSrc ++;
+    }
+    psMetadataAdd (header, PS_LIST_TAIL, "NSTARS", PS_DATA_S32 | PS_META_REPLACE, "NUMBER OF STARS", nSrc);
+
+    // MEF elements have XTENSION, not SIMPLE: remove this (replace with SIMPLE)
+    psMetadataLookupStr (&status, header, "XTENSION");
+    if (status)
+        psMetadataRemoveKey (header, "XTENSION");
+
+    // create file, write-out header
+    psMetadataAddS32 (header, PS_LIST_HEAD, "NAXIS", PS_META_REPLACE, "head data only", 0);
+    psMetadataAddBool (header, PS_LIST_HEAD, "SIMPLE", PS_META_REPLACE, "CMP file, not simple", false);
+
+    psFits *fits = psFitsOpen (filename, "w");
+    if (fits == NULL) {
+        psError(PS_ERR_IO, false, "can't open output file for write %s\n", filename);
+        return false;
+    }
+    // XXX what is the EXTNAME??
+    if (!psFitsWriteBlank(fits, header, "")) {
+        psError(PS_ERR_IO, false, "Writing header to %s\n", filename);
+        (void)psFitsClose(fits);
+        return false;
+    }
+    if (!psFitsClose(fits)) {
+        const psErrorCode code = psErrorCodeLast();
+
+        if (code == PS_ERR_BAD_FITS) {
+            psErrorClear();
+        } else {
+            psError(PS_ERR_IO, false, "Closing %s\n", filename);
+            return false;
+        }
+    }
+
+    // re-open, add data to end of file
+    FILE *f = fopen (filename, "a+");
+    if (f == NULL) {
+        psLogMsg ("WriteSourceOBJ", 3, "can't reopen output file for append %s\n", filename);
+        psError(PS_ERR_IO, false, "can't open output file for output %s\n", filename);
+        return false;
+    }
+
+    fseeko(f, 0, SEEK_END);
+
+    psLine *line = psLineAlloc (67);  // 66 is imclean-defined line length
+
+    // write sources with models first
+    for (i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+        pmModel *model = pmSourceSelectModel (source);
+        if (model == NULL)
+            continue;
+
+        PAR = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        type = pmSourceDophotType (source);
+        lsky = (PAR[PM_PAR_SKY] < 1.0) ? 0.0 : log10(PAR[PM_PAR_SKY]);
+
+        shape.sx  = PAR[PM_PAR_SXX];
+        shape.sy  = PAR[PM_PAR_SYY];
+        shape.sxy = PAR[PM_PAR_SXY];
+        axes = psEllipseShapeToAxes (shape);
+
+        psLineInit (line);
+        psLineAdd (line, "%6.1f ",  PAR[PM_PAR_XPOS]);
+        psLineAdd (line, "%6.1f ",  PAR[PM_PAR_YPOS]);
+        psLineAdd (line, "%6.3f ",  PS_MIN (99.0, source->psfMag + ZERO_POINT));
+        psLineAdd (line, "%03d ",   PS_MIN (999, (int)(1000*source->errMag)));
+        psLineAdd (line, "%2d ",    type);
+        psLineAdd (line, "%3.1f ",  lsky);
+        psLineAdd (line, "%6.3f ",  PS_MIN (99.0, source->extMag + ZERO_POINT));
+        psLineAdd (line, "%6.3f ",  PS_MIN (99.0, source->apMag  + ZERO_POINT));
+        psLineAdd (line, "%6.2f ",  axes.major);
+        psLineAdd (line, "%6.2f ",  axes.minor);
+        psLineAdd (line, "%5.1f\n", axes.theta);
+        fwrite (line->line, 1, line->Nline, f);
+    }
+    fclose (f);
+    psFree (line);
+    return true;
+}
+
+# define BYTES_STAR 66
+# define BLOCK 1000
+
+// elixir-style pseudo FITS table (header + ascii list)
+psArray *pmSourcesReadCMP (char *filename, psMetadata *header)
+{
+
+    bool status;
+    int Ninstar;
+    psF32 *PAR, *dPAR;
+    psEllipseShape shape;
+    psEllipseAxes axes;
+
+    // define PSF model type
+    int modelType = pmModelSetType ("PS_MODEL_GAUSS");
+
+    char *PSF_NAME = psMetadataLookupStr (&status, header, "PSF_NAME");
+    if (PSF_NAME != NULL) {
+        modelType = pmModelSetType (PSF_NAME);
+    }
+
+    // find config information for output header
+    float ZERO_POINT = psMetadataLookupF32 (&status, header, "ZERO_PT");
+    if (!status)
+        ZERO_POINT = 25.0;
+
+    // how many lines in the header?
+    long nLines = header->list->n;
+    off_t nBytes = nLines * 80;
+    if (nBytes % 2880) {
+        off_t nBlock = 1 + (off_t)(nBytes / 2880);
+        nBytes = nBlock * 2880;
+    }
+
+    // re-open, seek to end of header
+    FILE *f = fopen (filename, "r");
+    if (f == NULL) {
+        psLogMsg ("pmSourcesReadCMP", 3, "can't open output file for input %s\n", filename);
+        return NULL;
+    }
+
+    fseeko(f, nBytes, SEEK_SET);
+
+    // prepare array to store data
+    int nStars = psMetadataLookupS32 (&status, header, "NSTARS");
+    psArray *sources = psArrayAlloc (nStars);
+    sources->n = 0;
+
+    // we have fixed bytes / line : use that info
+    // XXX use the min of nStars and BLOCK?
+    char *buffer = psAlloc (BYTES_STAR*PS_MIN(nStars, BLOCK));
+
+    int Nextra = 0;
+    while (true) {
+        /* load next data block */
+        // XXX fix the use of two vars with different case -JH
+        off_t Nbytes = BYTES_STAR * BLOCK - Nextra;
+        off_t nbytes = fread (&buffer[Nextra], 1, Nbytes, f);
+        if (nbytes == 0) {
+            goto done_load;
+        }
+        nbytes += Nextra;
+
+        /* check line-by-line integrity */
+        char *c  = buffer;
+        char *c2 = NULL;
+        while (c < buffer + nbytes) {
+            for (c2 = c; *c2 == '\n'; c2++)
+                ;
+            if (c2 > c) { /* extra return chars */
+                memmove (c, c2, (int)(buffer + nbytes - c2));
+                int Nskip = c2 - c;
+                nbytes -= Nskip;
+                bzero (buffer + nbytes, Nskip);
+                psLogMsg (__func__, 4, "deleted %d extra return chars\n", Nskip);
+            }
+            c2 = strchr (c, '\n');
+            if (c2 == (char *) NULL) {
+                goto done_check;
+            }
+            c2++;
+            if ((c2 - c) != BYTES_STAR) { /* bad line, delete it */
+                memmove (c, c2, (int)(buffer + nbytes - c2));
+                int Nskip = c2 - c;
+                nbytes -= Nskip;
+                bzero (buffer + nbytes, Nskip);
+                psLogMsg (__func__, 4, "deleted line, %d extra chars\n", Nskip);
+            } else {
+                c = c2;
+            }
+        }
+done_check:
+
+        /* extract data for stars */
+        Ninstar = nbytes / BYTES_STAR;
+        Nextra = nbytes % BYTES_STAR;
+        for (int j = 0; j < Ninstar; j++) {
+            psString line = psStringNCopy (&buffer[j*BYTES_STAR], BYTES_STAR);
+
+            psList *list = psStringSplit (line, " ", false);
+            psArray *array = psListToArray (list);
+
+            // XXX this is a bit cheap: I don't even attempt to interpret the
+            // sextractor / dophot analysis to distinguish stars and galaxies
+            // your milage may vary...
+            pmSource *source = pmSourceAlloc ();
+            pmModel *model = pmModelAlloc (modelType);
+
+            PAR = model->params->data.F32;
+            dPAR = model->dparams->data.F32;
+
+            PAR[PM_PAR_SKY] = pow (atof (array->data[5]), 10.0);
+            PAR[PM_PAR_XPOS] = atof (array->data[0]);
+            PAR[PM_PAR_YPOS] = atof (array->data[1]);
+            source->psfMag = atof (array->data[2]);
+            source->extMag = atof (array->data[6]);
+            source->errMag = atof (array->data[3]) / 1000.0;
+            source->apMag  = atof (array->data[7]);
+            axes.major     = atof (array->data[8]);
+            axes.minor     = atof (array->data[9]);
+            axes.theta  = atof (array->data[10]);
+
+            shape = psEllipseAxesToShape (axes);
+
+            PAR[4] = shape.sx;
+            PAR[5] = shape.sy;
+            PAR[6] = shape.sxy;
+
+            source->modelPSF = model;
+            source->type = PM_SOURCE_TYPE_STAR;
+
+            psArrayAdd (sources, 100, source);
+
+            psFree (line);
+            psFree (list);
+            psFree (array);
+            psFree (source);
+
+        }
+    }
+done_load:
+
+    // XXX if sources->n != nStars, give an error?
+    psFree (buffer);
+
+    fclose (f);
+    return (sources);
+}
Index: /tags/RHL_v1_1/psModules/src/objects/pmSourceIO_OBJ.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSourceIO_OBJ.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSourceIO_OBJ.c	(revision 9533)
@@ -0,0 +1,96 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-10-13 02:29:14 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmModel.h"
+#include "pmPSF.h"
+#include "pmSource.h"
+#include "pmModelGroup.h"
+#include "pmSourceIO.h"
+
+// dophot-style output list with fixed line width
+bool pmSourcesWriteOBJ (psArray *sources, char *filename)
+{
+
+    int type;
+    psF32 *PAR, *dPAR;
+    float dmag, apResid;
+    psEllipseShape shape;
+    psEllipseAxes axes;
+
+    psTimerStart ("string");
+
+    psLine *line = psLineAlloc (104);  // 104 is dophot-defined line length
+
+    FILE *f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg (__func__, 3, "can't open output file for output %s\n", filename);
+        return false;
+    }
+
+    // write sources with models
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+        pmModel *model = pmSourceSelectModel (source);
+        if (model == NULL)
+            continue;
+
+        PAR = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        dmag = dPAR[PM_PAR_I0] / PAR[PM_PAR_I0];
+        type = pmSourceDophotType (source);
+        if ((source->apMag < 99.0) && (source->psfMag < 99.0)) {
+            apResid = source->apMag - source->psfMag;
+        } else {
+            apResid = 0.0;
+        }
+
+        shape.sx  = PAR[PM_PAR_SXX];
+        shape.sy  = PAR[PM_PAR_SYY];
+        shape.sxy = PAR[PM_PAR_SXY];
+        axes = psEllipseShapeToAxes (shape);
+
+        psLineInit (line);
+        psLineAdd (line, "%3d",   type);
+        psLineAdd (line, "%8.2f", PAR[PM_PAR_XPOS]);
+        psLineAdd (line, "%8.2f", PAR[PM_PAR_YPOS]);
+        psLineAdd (line, "%8.3f", source->psfMag);
+        psLineAdd (line, "%6.3f", dmag);
+        psLineAdd (line, "%9.2f", PAR[PM_PAR_SKY]);
+        psLineAdd (line, "%9.3f", axes.major);
+        psLineAdd (line, "%9.3f", axes.minor);
+        psLineAdd (line, "%7.2f", axes.theta);
+        psLineAdd (line, "%8.3f", source->extMag);
+        psLineAdd (line, "%8.3f", source->apMag);
+        psLineAdd (line, "%8.2f\n", apResid);
+        fwrite (line->line, 1, line->Nline, f);
+    }
+    fclose (f);
+    psFree (line);
+    fprintf (stderr, "%f seconds for %d objects\n", psTimerMark ("string"), (int)sources->n);
+    return true;
+}
Index: /tags/RHL_v1_1/psModules/src/objects/pmSourceIO_RAW.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSourceIO_RAW.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSourceIO_RAW.c	(revision 9533)
@@ -0,0 +1,272 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-10-12 21:41:22 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmModel.h"
+#include "pmPSF.h"
+#include "pmSource.h"
+#include "pmModelGroup.h"
+#include "pmSourcePhotometry.h"
+#include "pmSourceIO.h"
+
+/***** Text Output Methods *****/
+bool pmSourcesWriteRAW (psArray *sources, char *filename)
+{
+
+    char *name = (char *) psAlloc (strlen(filename) + 10);
+
+    sprintf (name, "%s.psf.dat", filename);
+    pmSourcesWritePSFs (sources, name);
+
+    sprintf (name, "%s.ext.dat", filename);
+    pmSourcesWriteEXTs (sources, name);
+
+    sprintf (name, "%s.nul.dat", filename);
+    pmSourcesWriteNULLs (sources, name);
+
+    sprintf (name, "%s.mnt.dat", filename);
+    pmMomentsWriteText (sources, name);
+
+    psFree (name);
+    return true;
+}
+
+// write the PSF sources to an output file
+bool pmSourcesWritePSFs (psArray *sources, char *filename)
+{
+
+    double dPos;
+    int i, j;
+    FILE *f;
+    psF32 *PAR, *dPAR;
+    pmModel  *model;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg (__func__, 3, "can't open output file for PSFs: %s\n", filename);
+        return false;
+    }
+
+    // write sources with models first
+    for (i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+        if (source->type != PM_SOURCE_TYPE_STAR)
+            continue;
+        model = source->modelPSF;
+        if (model == NULL)
+            continue;
+
+        PAR  = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        // dPos is positional error, dMag is mag error
+        dPos = hypot (dPAR[PM_PAR_XPOS], dPAR[PM_PAR_YPOS]);
+
+        fprintf (f, "%7.1f %7.1f  %7.1f %8.4f  %7.4f %7.4f  ",
+                 PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], PAR[PM_PAR_SKY],
+                 source->psfMag, source->errMag, dPos);
+
+        for (j = 4; j < model->params->n; j++) {
+            fprintf (f, "%9.6f ", PAR[j]);
+        }
+        fprintf (f, " : ");
+        for (j = 4; j < model->params->n; j++) {
+            fprintf (f, "%9.6f ", dPAR[j]);
+        }
+        fprintf (f, ": %8.4f %2d %#5x %7.3f %7.3f  %7.1f %7.2f %4.2f %4d %2d\n",
+                 source[0].apMag, source[0].type, source[0].mode,
+                 log10(model[0].chisq/model[0].nDOF),
+                 log10(model[0].chisqNorm/model[0].nDOF),
+                 source[0].moments->SN,
+                 model[0].radiusTMP,
+                 source[0].pixWeight,
+                 model[0].nDOF,
+                 model[0].nIter);
+    }
+    fclose (f);
+    return true;
+}
+
+// dump the sources to an output file
+bool pmSourcesWriteEXTs (psArray *sources, char *filename)
+{
+
+    double dPos;
+    int i, j;
+    FILE *f;
+    psF32 *PAR, *dPAR;
+    pmModel  *model;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg ("pmModelWriteEXTs", 3, "can't open output file for moments%s\n", filename);
+        return false;
+    }
+
+    // write sources with models first
+    for (i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+
+        if (source->type != PM_SOURCE_TYPE_EXTENDED)
+            continue;
+
+        model = source->modelEXT;
+        if (model == NULL)
+            continue;
+
+        PAR  = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        // dPos is shape error
+        // XXX these are hardwired for SGAUSS
+        dPos = hypot ((dPAR[PM_PAR_SXX] / PAR[PM_PAR_SXX]), (dPAR[PM_PAR_SYY] / PAR[PM_PAR_SYY]));
+
+        fprintf (f, "%7.1f %7.1f  %7.1f %8.4f  %7.4f %7.4f  ",
+                 PAR[PM_PAR_XPOS], PAR[PM_PAR_YPOS], PAR[PM_PAR_SKY],
+                 source->extMag, source->errMag, dPos);
+
+        for (j = 4; j < model->params->n; j++) {
+            fprintf (f, "%9.6f ", PAR[j]);
+        }
+        fprintf (f, " : ");
+        for (j = 4; j < model->params->n; j++) {
+            fprintf (f, "%9.6f ", dPAR[j]);
+        }
+        fprintf (f, ": %7.4f  %2d %#5x %7.3f %7.3f  %7.1f %7.2f %4.2f %4d %2d\n",
+                 source->apMag,
+                 source[0].type, source[0].mode,
+                 log10(model[0].chisq/model[0].nDOF),
+                 log10(model[0].chisqNorm/model[0].nDOF),
+                 source[0].moments->SN,
+                 model[0].radiusTMP,
+                 source[0].pixWeight,
+                 model[0].nDOF,
+                 model[0].nIter);
+    }
+    fclose (f);
+    return true;
+}
+
+// dump the sources to an output file
+bool pmSourcesWriteNULLs (psArray *sources, char *filename)
+{
+
+    int i;
+    FILE *f;
+    pmMoments *moment = NULL;
+    pmSource *source = NULL;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg ("DumpObjects", 3, "can't open output file for moments%s\n", filename);
+        return false;
+    }
+
+    pmMoments *empty = pmMomentsAlloc ();
+
+    // write sources with models first
+    for (i = 0; i < sources->n; i++) {
+        source = sources->data[i];
+
+        // skip these sources (in PSF or EXT)
+        if (source->type == PM_SOURCE_TYPE_STAR)
+            continue;
+        if (source->type == PM_SOURCE_TYPE_EXTENDED)
+            continue;
+
+        if (source->moments == NULL) {
+            moment = empty;
+        } else {
+            moment = source->moments;
+        }
+
+        fprintf (f, "%5d %5d  %7.1f  %7.1f %7.1f  %6.3f %6.3f  %8.1f %7.1f %7.1f %7.1f  %4d %2d\n",
+                 source->peak->x, source->peak->y, source->peak->counts,
+                 moment->x, moment->y,
+                 moment->Sx, moment->Sy,
+                 moment->Sum, moment->Peak,
+                 moment->Sky, moment->SN,
+                 moment->nPixels, source->type);
+    }
+    fclose (f);
+    psFree (empty);
+    return true;
+}
+
+// write the moments to an output file
+bool pmMomentsWriteText (psArray *sources, char *filename)
+{
+
+    int i;
+    FILE *f;
+    pmSource *source = NULL;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg ("pmMomentsWriteText", 3, "can't open output file for moments%s\n", filename);
+        return false;
+    }
+
+    for (i = 0; i < sources->n; i++) {
+        source = sources->data[i];
+        if (source->moments == NULL)
+            continue;
+        fprintf (f, "%5d %5d  %7.1f  %7.1f %7.1f  %6.3f %6.3f  %10.1f %7.1f %7.1f %7.1f  %4d %2d %#5x\n",
+                 source->peak->x, source->peak->y, source->peak->counts,
+                 source->moments->x, source->moments->y,
+                 source->moments->Sx, source->moments->Sy,
+                 source->moments->Sum, source->moments->Peak,
+                 source->moments->Sky, source->moments->SN,
+                 source->moments->nPixels, source->type, source->mode);
+    }
+    fclose (f);
+    return true;
+}
+
+// write the peaks to an output file
+bool pmPeaksWriteText (psArray *peaks, char *filename)
+{
+
+    int i;
+    FILE *f;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg ("pmPeaksWriteText", 3, "can't open output file for peaks%s\n", filename);
+        return false;
+    }
+
+    for (i = 0; i < peaks->n; i++) {
+        pmPeak *peak = peaks->data[i];
+        if (peak == NULL)
+            continue;
+        fprintf (f, "%5d %5d  %7.1f\n",
+                 peak->x, peak->y, peak->counts);
+    }
+    fclose (f);
+    return true;
+}
+
Index: /tags/RHL_v1_1/psModules/src/objects/pmSourceIO_SX.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSourceIO_SX.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSourceIO_SX.c	(revision 9533)
@@ -0,0 +1,88 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-22 12:24:38 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAview.h"
+#include "pmFPAfile.h"
+
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmModel.h"
+#include "pmPSF.h"
+#include "pmSource.h"
+#include "pmModelGroup.h"
+#include "pmSourceIO.h"
+
+// elixir-mode / sextractor-style output list with fixed line width
+bool pmSourcesWriteSX (psArray *sources, char *filename)
+{
+
+    psF32 *PAR, *dPAR;
+    psEllipseShape shape;
+    psEllipseAxes axes;
+
+    psLine *line = psLineAlloc (110);  // 110 is sextractor line length
+
+    FILE *f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg (__func__, 3, "can't open output file for output %s\n", filename);
+        return false;
+    }
+
+    // write sources with models
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+        pmModel *model = pmSourceSelectModel (source);
+        if (model == NULL)
+            continue;
+
+        PAR = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        // pmSourceSextractType (source, &type, &flags);
+
+        shape.sx  = PAR[PM_PAR_SXX];
+        shape.sy  = PAR[PM_PAR_SYY];
+        shape.sxy = PAR[PM_PAR_SXY];
+        axes = psEllipseShapeToAxes (shape);
+
+        psLineInit (line);
+        psLineAdd (line, "%5.2f",  0.0); // should be type
+        psLineAdd (line, "%11.3f", PAR[PM_PAR_XPOS]);
+        psLineAdd (line, "%11.3f", PAR[PM_PAR_YPOS]);
+        psLineAdd (line, "%9.4f",  source->psfMag);
+        psLineAdd (line, "%9.4f",  source->errMag);
+        psLineAdd (line, "%13.4f", PAR[PM_PAR_SKY]);
+        psLineAdd (line, "%9.2f",  axes.major);
+        psLineAdd (line, "%9.2f",  axes.minor);
+        psLineAdd (line, "%6.1f",  axes.theta);
+        psLineAdd (line, "%9.4f",  source->extMag);
+        psLineAdd (line, "%9.4f",  source->apMag);
+        psLineAdd (line, "%4d\n",  0); // should be flags
+        fwrite (line->line, 1, line->Nline, f);
+    }
+    fclose (f);
+    psFree (line);
+    return true;
+}
+
+// XXX need to fix the FWHM / shape stuff,
+// XXX make sure we are using the correct mags, etc
Index: /tags/RHL_v1_1/psModules/src/objects/pmSourcePhotometry.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSourcePhotometry.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSourcePhotometry.c	(revision 9533)
@@ -0,0 +1,410 @@
+/** @file  pmSourcePhotometry.c
+ *
+ *  @author EAM, IfA; GLG, MHPCC
+ *
+ *  @version $Revision: 1.10 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-10-13 02:29:14 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmGrowthCurve.h"
+#include "pmModel.h"
+#include "pmPSF.h"
+#include "pmSource.h"
+#include "pmModelGroup.h"
+#include "pmSourcePhotometry.h"
+
+# define DO_SKY 0
+
+static float AP_MIN_SN = 0.0;
+
+bool pmSourceMagnitudesInit (psMetadata *config)
+{
+
+    bool status;
+
+    float limit = psMetadataLookupF32 (&status, config, "AP_MIN_SN");
+    if (status) {
+        AP_MIN_SN = limit;
+    }
+    return true;
+}
+
+/**
+    this function is used to calculate the three defined source magnitudes:
+    - apMag  : only if S/N > AP_MIN_SN
+             : is optionally corrected for curve-of-growth if:
+        - the source is a STAR (PSF)
+        - the option is selected (how??)
+    - psfMag : all sources with non-NULL modelPSF
+             : is optionally corrected for aperture residual if:
+        - the source is a STAR (PSF)
+        - the option is selected (how??)
+ 
+    - extMag : all sources with non-NULL modelEXT
+**/
+
+// XXX EAM : masked region should be (optionally) elliptical
+bool pmSourceMagnitudes (pmSource *source, pmPSF *psf, pmSourcePhotometryMode mode)
+{
+
+    int status = false;
+    bool isPSF;
+    float x, y;
+    float rflux;
+    float SN;
+    pmModel *model;
+
+    source->psfMag = 99.0;
+    source->extMag = 99.0;
+    source->errMag = 99.0;
+    source->apMag  = 99.0;
+
+    switch (source->type) {
+    case PM_SOURCE_TYPE_STAR:
+        model = source->modelPSF;
+        if (model == NULL)
+            return false;
+        isPSF = true;
+        break;
+
+    case PM_SOURCE_TYPE_EXTENDED:
+        model = source->modelEXT;
+        if (model == NULL)
+            return false;
+        isPSF = false;
+        break;
+
+    default:
+        return false;
+    }
+
+    if (model->dparams->data.F32[PM_PAR_I0] > 0) {
+        SN = model->params->data.F32[PM_PAR_I0] / model->dparams->data.F32[PM_PAR_I0];
+        source->errMag = 1.0 / SN;
+    } else {
+        SN = 0.0;
+        source->errMag = 0.0;
+    }
+    x = model->params->data.F32[PM_PAR_XPOS];
+    y = model->params->data.F32[PM_PAR_YPOS];
+
+    // measure object model photometry
+    status = pmSourcePhotometryModel (&source->psfMag, source->modelPSF);
+    status = pmSourcePhotometryModel (&source->extMag, source->modelEXT);
+    // for PSFs, correct both apMag and psfMag to same system, consistent with infinite flux star in aperture RADIUS
+    if ((mode & PM_SOURCE_PHOT_APCORR) && isPSF && psf) {
+        // convert to the equivalent 2D model?
+        source->psfMag += psPolynomial4DEval (psf->ApTrend, x, y, 0.0, 0.0);
+    }
+
+    if (SN < AP_MIN_SN)
+        return false;
+
+    // replace source flux
+    // XXX test to see if source has been subtracted?
+    pmModelAdd (source->pixels, source->mask, model, false, false);
+
+    // set aperture mask circle to model radius
+    psImageKeepCircle (source->mask, x, y, model->radiusTMP, "OR", PM_MASK_MARK);
+
+    // measure the weight of included pixels
+    if (mode & PM_SOURCE_PHOT_WEIGHT) {
+        pmSourcePixelWeight (&source->pixWeight, model, source->pixels, source->mask);
+    }
+
+    // measure object aperture photometry
+    status = pmSourcePhotometryAper  (&source->apMag, model, source->pixels, source->mask);
+
+    // for PSFs, correct both apMag and psfMag to same system, consistent with infinite flux star in aperture RADIUS
+    if (status && isPSF && psf) {
+        if (psf->growth && (mode & PM_SOURCE_PHOT_GROWTH)) {
+            source->apMag += pmGrowthCurveCorrect (psf->growth, model->radiusTMP);
+        }
+        if (mode & PM_SOURCE_PHOT_APCORR) {
+            rflux   = pow (10.0, 0.4*source->psfMag);
+            source->apMag  -= PS_SQR(model->radiusTMP)*rflux * psf->skyBias + psf->skySat / rflux;
+        }
+    }
+
+    // unmask aperture
+    psImageKeepCircle (source->mask, x, y, model->radiusTMP, "AND", NOT_U8(PM_MASK_MARK));
+
+    // subtract object, leave local sky
+    pmModelSub (source->pixels, source->mask, model, false, false);
+
+    return status;
+}
+
+/*
+aprMag' - fitMag = flux*skySat + r^2*rflux*skyBias + ApTrend(x,y)
+(aprMag - flux*skySat - r^2*rflux*skyBias) - fitMAg = ApTrend(x,y)
+(aprMag - flux*skySat - r^2*rflux*skyBias) = fitMAg + ApTrend(x,y)
+ 
+*/
+
+// return source model magnitude
+bool pmSourcePhotometryModel (float *fitMag, pmModel *model)
+{
+
+    float fitSum = 0;
+    *fitMag = 99.0;
+
+    if (model == NULL) {
+        return false;
+    }
+
+    // measure fitMag
+    pmModelFlux modelFluxFunc = pmModelFlux_GetFunction (model->type);
+    fitSum = modelFluxFunc (model->params);
+    if (fitSum <= 0)
+        return false;
+    if (!isfinite(fitSum))
+        return false;
+    *fitMag = -2.5*log10(fitSum);
+
+    return (true);
+}
+
+// return source aperture magnitude
+bool pmSourcePhotometryAper (float *apMag, pmModel *model, psImage *image, psImage *mask)
+{
+    float apSum = 0;
+    float sky = 0;
+    *apMag = 99.0;
+
+    if (model == NULL) {
+        return false;
+    }
+
+    if (DO_SKY) {
+        sky = model->params->data.F32[PM_PAR_SKY];
+    } else {
+        sky = 0;
+    }
+
+    // measure apMag
+    for (int ix = 0; ix < image->numCols; ix++) {
+        for (int iy = 0; iy < image->numRows; iy++) {
+            if (mask->data.U8[iy][ix])
+                continue;
+            apSum += image->data.F32[iy][ix] - sky;
+        }
+    }
+    if (apSum <= 0)
+        return false;
+
+    *apMag = -2.5*log10(apSum);
+    return (true);
+}
+
+// return source aperture magnitude
+bool pmSourcePixelWeight (float *pixWeight, pmModel *model, psImage *image, psImage *mask)
+{
+    float modelSum = 0;
+    float validSum = 0;
+    float sky = 0;
+    float value;
+
+    int Xo, Yo, dP;
+    int dX, DX, NX;
+    int dY, DY, NY;
+
+    *pixWeight = 0.0;
+
+    if (model == NULL)
+        return false;
+
+    // we only care about the value of the object model, not the local sky
+    if (DO_SKY) {
+        sky = model->params->data.F32[PM_PAR_SKY];
+    } else {
+        sky = 0;
+    }
+
+    // the model function returns the source flux at a position
+    pmModelFunc modelFunc = pmModelFunc_GetFunction (model->type);
+    psVector *coord = psVectorAlloc(2, PS_TYPE_F32);
+    coord->n = 2;
+
+    psVector *params = model->params;
+
+    Xo = params->data.F32[PM_PAR_XPOS];
+    Yo = params->data.F32[PM_PAR_YPOS];
+
+    dX = Xo - image->col0;
+    dP = image->numCols - dX;
+    DX = PS_MAX(dX, dP);
+    NX = image->numCols;
+
+    dY = Yo - image->row0;
+    dP = image->numRows - dY;
+    DY = PS_MAX(dY, dP);
+    NY = image->numRows;
+
+    // measure modelSum and validSum
+    // XXX this does not work for sources near the edge: we need to measure for
+    // a full square region
+    for (int ix = -DX; ix < DX + 1; ix++) {
+        int mx = ix + dX;
+        for (int iy = -DY; iy < DY + 1; iy++) {
+            int my = iy + dY;
+
+            coord->data.F32[0] = (psF32) (ix + Xo);
+            coord->data.F32[1] = (psF32) (iy + Yo);
+
+            // for the full model, add all points
+            value = modelFunc (NULL, params, coord) - sky;
+            modelSum += value;
+
+            // include count only the unmasked pixels within the image area
+            if (mx < 0)
+                continue;
+            if (my < 0)
+                continue;
+            if (mx >= NX)
+                continue;
+            if (my >= NY)
+                continue;
+            if (mask->data.U8[my][mx])
+                continue;
+
+            validSum += value;
+        }
+    }
+    psFree (coord);
+
+    if (validSum <= 0)
+        return false;
+
+    *pixWeight = validSum / modelSum;
+    return (true);
+}
+
+double pmSourceCrossProduct (const pmSource *Mi,
+                             const pmSource *Mj,
+                             const bool unweighted_sum) // should the cross product be weighted?
+{
+
+    int Xs, Xe, Ys, Ye;
+    int xi, xj, yi, yj;
+    int xIs, xJs, yIs, yJs;
+    int xIe, yIe;
+    double flux, wt;
+
+    const psImage *Pi = Mi->pixels;
+    const psImage *Pj = Mj->pixels;
+
+    const psImage *Wi = Mi->weight;
+
+    const psImage *Ti = Mi->mask;
+    const psImage *Tj = Mj->mask;
+
+    Xs = PS_MAX (Pi->col0, Pj->col0);
+    Xe = PS_MIN (Pi->col0 + Pi->numCols, Pj->col0 + Pj->numCols);
+
+    Ys = PS_MAX (Pi->row0, Pj->row0);
+    Ye = PS_MIN (Pi->row0 + Pi->numRows, Pj->row0 + Pj->numRows);
+
+    xIs = Xs - Pi->col0;
+    xJs = Xs - Pj->col0;
+    yIs = Ys - Pi->row0;
+    yJs = Ys - Pj->row0;
+
+    xIe = Xe - Pi->col0;
+    yIe = Ye - Pi->row0;
+
+    // note that this is addressing the same image pixels,
+    // though only if both are source not model images
+    flux = 0;
+    for (yi = yIs, yj = yJs; yi < yIe; yi++, yj++) {
+        for (xi = xIs, xj = xJs; xi < xIe; xi++, xj++) {
+            if (Ti->data.U8[yi][xi])
+                continue;
+            if (Tj->data.U8[yj][xj])
+                continue;
+
+            if (unweighted_sum) {
+                flux += (Pi->data.F32[yi][xi] * Pj->data.F32[yj][xj]);
+            } else {
+                wt = Wi->data.F32[yi][xi];
+                if (wt > 0) {
+                    flux += (Pi->data.F32[yi][xi] * Pj->data.F32[yj][xj]) / wt;
+                }
+            }
+        }
+    }
+    return flux;
+}
+
+double pmSourceCrossWeight(const pmSource *Mi,
+                           const pmSource *Mj,
+                           const bool unweighted_sum) // should the cross product be weighted?
+{
+
+    int Xs, Xe, Ys, Ye;
+    int xi, xj, yi, yj;
+    int xIs, xJs, yIs, yJs;
+    int xIe, yIe;
+    double flux, wt;
+
+    const psImage *Pi = Mi->pixels;
+    const psImage *Pj = Mj->pixels;
+
+    const psImage *Wi = Mi->weight;
+
+    const psImage *Ti = Mi->mask;
+    const psImage *Tj = Mj->mask;
+
+    Xs = PS_MAX (Pi->col0, Pj->col0);
+    Xe = PS_MIN (Pi->col0 + Pi->numCols, Pj->col0 + Pj->numCols);
+
+    Ys = PS_MAX (Pi->row0, Pj->row0);
+    Ye = PS_MIN (Pi->row0 + Pi->numRows, Pj->row0 + Pj->numRows);
+
+    xIs = Xs - Pi->col0;
+    xJs = Xs - Pj->col0;
+    yIs = Ys - Pi->row0;
+    yJs = Ys - Pj->row0;
+
+    xIe = Xe - Pi->col0;
+    yIe = Ye - Pi->row0;
+
+    // note that this is addressing the same image pixels,
+    // though only if both are source not model images
+    flux = 0;
+    for (yi = yIs, yj = yJs; yi < yIe; yi++, yj++) {
+        for (xi = xIs, xj = xJs; xi < xIe; xi++, xj++) {
+            if (Ti->data.U8[yi][xi])
+                continue;
+            if (Tj->data.U8[yj][xj])
+                continue;
+
+            if (unweighted_sum) {
+                flux++;
+            } else {
+                wt = Wi->data.F32[yi][xi];
+                if (wt > 0) {
+                    flux += 1.0 / wt;
+                }
+            }
+        }
+    }
+    return flux;
+}
+
Index: /tags/RHL_v1_1/psModules/src/objects/pmSourcePhotometry.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSourcePhotometry.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSourcePhotometry.h	(revision 9533)
@@ -0,0 +1,53 @@
+/** @file  pmSourcePhotometry.h
+ *
+ *  @author EAM, IfA; GLG, MHPCC
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-10-13 02:24:34 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+# ifndef PM_SOURCE_PHOTOMETRY_H
+# define PM_SOURCE_PHOTOMETRY_H
+
+/**
+ *
+ * The function returns both the magnitude of the fit, defined as -2.5log(flux),
+ * where the flux is integrated under the model, theoretically from a radius of 0
+ * to infinity. In practice, we integrate the model beyond 50sigma.  The aperture magnitude is
+ * defined as -2.5log(flux) , where the flux is summed for all pixels which are
+ * not excluded by the aperture mask. The model flux is calculated by calling the
+ * model-specific function provided by pmModelFlux_GetFunction.
+ *
+ * XXX: must code this.
+ *
+ */
+
+typedef enum {
+    PM_SOURCE_PHOT_NONE   = 0x0000,
+    PM_SOURCE_PHOT_GROWTH = 0x0001,
+    PM_SOURCE_PHOT_APCORR = 0x0002,
+    PM_SOURCE_PHOT_WEIGHT = 0x0004,
+} pmSourcePhotometryMode;
+
+bool pmSourcePhotometryModel(
+    float *fitMag,                      ///< integrated fit magnitude
+    pmModel *model                      ///< model used for photometry
+);
+
+bool pmSourcePhotometryAper(
+    float   *apMag,                     ///< aperture flux magnitude
+    pmModel *model,                     ///< model used for photometry
+    psImage *image,                     ///< image pixels to be used
+    psImage *mask                       ///< mask of pixels to ignore
+);
+
+bool pmSourceMagnitudesInit (psMetadata *config);
+bool pmSourceMagnitudes (pmSource *source, pmPSF *psf, pmSourcePhotometryMode mode);
+double pmSourceCrossProduct(const pmSource *Mi, const pmSource *Mj, const bool unweighted_sum);
+double pmSourceCrossWeight(const pmSource *Mi, const pmSource *Mj, const bool unweighted_sum);
+bool pmSourcePixelWeight (float *pixWeight, pmModel *model, psImage *image, psImage *mask);
+
+# endif /* PM_SOURCE_PHOTOMETRY_H */
Index: /tags/RHL_v1_1/psModules/src/objects/pmSourceSky.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSourceSky.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSourceSky.c	(revision 9533)
@@ -0,0 +1,133 @@
+/** @file  pmSourceSky.c
+ *
+ *  Functions to measure the local sky and sky variance for sources on images
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA: significant modifications.
+ *
+ *  @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-09-15 09:49:01 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmModel.h"
+#include "pmSource.h"
+#include "pmSourceSky.h"
+
+/******************************************************************************
+pmSource *pmSourceLocalSky(source, statsOptions, Radius): this
+routine creates a new pmSource.moments element if needed and sets pmSource.pmMoments.sky
+ 
+The sky value is set from the pixels in the square annulus surrounding the
+peak pixel.
+ 
+The source.pixels and source.mask must already exist
+*****************************************************************************/
+
+bool pmSourceLocalSky(
+    pmSource *source,
+    psStatsOptions statsOptions,
+    psF32 Radius)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_IMAGE_NON_NULL(source->pixels, false);
+    PS_ASSERT_IMAGE_NON_NULL(source->mask, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_INT_POSITIVE(Radius, false);
+    PS_ASSERT_INT_NONNEGATIVE(Radius, false);
+
+    psStatsOptions statistic = psStatsSingleOption(statsOptions);
+    if (statistic == 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Multiple or no statistics specified: %x\n", statsOptions);
+        return NULL;
+    }
+
+    psImage *image = source->pixels;
+    psImage *mask  = source->mask;
+    pmPeak *peak  = source->peak;
+    psRegion srcRegion;
+
+    srcRegion = psRegionForSquare(peak->x, peak->y, Radius);
+    srcRegion = psRegionForImage(mask, srcRegion);
+
+    psImageMaskRegion(mask, srcRegion, "OR", PM_MASK_MARK);
+    psStats *myStats = psStatsAlloc(statsOptions);
+    myStats = psImageStats(myStats, image, mask, 0xff);
+    psImageMaskRegion(mask, srcRegion, "AND", NOT_U8(PM_MASK_MARK));
+    double value = psStatsGetValue(myStats, statistic);
+    psFree(myStats);
+
+    if (isnan(value)) {
+        psTrace("psModules.objects", 3, "---- %s(false) end ----\n", __func__);
+        return(false);
+    }
+    if (source->moments == NULL) {
+        source->moments = pmMomentsAlloc();
+    }
+    source->moments->Sky = value;
+    psTrace("psModules.objects", 3, "---- %s(true) end ----\n", __func__);
+    return (true);
+}
+
+// A complementary function to pmSourceLocalSky: calculate the local median variance
+bool pmSourceLocalSkyVariance(
+    pmSource *source,
+    psStatsOptions statsOptions,
+    psF32 Radius)
+{
+    psTrace("psModules.objects", 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_IMAGE_NON_NULL(source->weight, false);
+    PS_ASSERT_IMAGE_NON_NULL(source->mask, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_INT_POSITIVE(Radius, false);
+    PS_ASSERT_INT_NONNEGATIVE(Radius, false);
+
+    psStatsOptions statistic = psStatsSingleOption(statsOptions);
+    if (statistic == 0) {
+        psError(PS_ERR_BAD_PARAMETER_VALUE, false, "Multiple or no statistics specified: %x\n", statsOptions);
+        return NULL;
+    }
+
+    psImage *image = source->weight;
+    psImage *mask  = source->mask;
+    pmPeak *peak  = source->peak;
+    psRegion srcRegion;
+
+    srcRegion = psRegionForSquare(peak->x, peak->y, Radius);
+    srcRegion = psRegionForImage(mask, srcRegion);
+
+    psImageMaskRegion(mask, srcRegion, "OR", PM_MASK_MARK);
+    psStats *myStats = psStatsAlloc(statsOptions);
+    myStats = psImageStats(myStats, image, mask, 0xff);
+    psImageMaskRegion(mask, srcRegion, "AND", NOT_U8(PM_MASK_MARK));
+    double value = psStatsGetValue(myStats, statistic);
+    psFree(myStats);
+
+    if (isnan(value)) {
+        psTrace("psModules.objects", 3, "---- %s(false) end ----\n", __func__);
+        return(false);
+    }
+    if (source->moments == NULL) {
+        source->moments = pmMomentsAlloc();
+    }
+    source->moments->dSky = value;
+    psTrace("psModules.objects", 3, "---- %s(true) end ----\n", __func__);
+    return (true);
+}
Index: /tags/RHL_v1_1/psModules/src/objects/pmSourceSky.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/pmSourceSky.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/pmSourceSky.h	(revision 9533)
@@ -0,0 +1,42 @@
+/** @file  pmSourceSky.h
+ *
+ *  @author EAM, IfA; GLG, MHPCC
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:01:05 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+# ifndef PM_SOURCE_SKY_H
+# define PM_SOURCE_SKY_H
+
+/** pmSourceLocalSky()
+ *
+ * Measure the local sky in the vicinity of the given source. The Radius
+ * defines the square aperture in which the moments will be measured. This
+ * function assumes the source pixels have been defined, and that the value of
+ * Radius here is smaller than the value of Radius used to define the pixels. The
+ * annular region not contained within the radius defined here is used to measure
+ * the local background in the vicinity of the source. The local background
+ * measurement uses the specified statistic passed in via the statsOptions entry.
+ * This function allocates the pmMoments structure. The resulting sky is used to
+ * set the value of the pmMoments.sky element of the provided pmSource structure.
+ *
+ */
+bool pmSourceLocalSky(
+    pmSource *source,   ///< The input image (float)
+    psStatsOptions statsOptions, ///< The statistic used in calculating the background sky
+    float Radius   ///< The inner radius of the square annulus to exclude
+);
+
+
+// A complementary function to pmSourceLocalSky: calculate the local sky variance
+bool pmSourceLocalSkyVariance(
+    pmSource *source,   ///< The input image (float)
+    psStatsOptions statsOptions, ///< The statistic used in calculating the background sky
+    float Radius   ///< The inner radius of the square annulus to exclude
+);
+
+# endif /* PM_SOURCE_PHOTOMETRY_H */
Index: /tags/RHL_v1_1/psModules/src/objects/psVectorBracket.c
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/psVectorBracket.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/psVectorBracket.c	(revision 9533)
@@ -0,0 +1,137 @@
+/** @file  psVectorBracket.c
+ *
+ *  Vector Bracketing tools
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-10-05 20:47:47 $
+ *
+ *  Copyright 2006 Institute for Astronomy, University of Hawaii
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pslib.h>
+# include "psVectorBracket.h"
+
+// return the last entry below or first entry above key value
+int psVectorBracket(const psVector *index, psF32 key, bool above)
+{
+
+    int N;
+    int Nlo = 0;
+    int Nhi = index->n;
+
+    if (above) {
+        while (Nhi - Nlo > 10) {
+            N = 0.5*(Nlo + Nhi);
+            if (index->data.F32[N] > key) {
+                Nhi = N;
+            } else {
+                Nlo = N - 1;
+            }
+        }
+        // at this point, index[Nhi] > key >= index[Nlo]
+        N = Nlo;
+        while ((index->data.F32[N] <= key) && (N < Nhi)) {
+            N++;
+        }
+        return (N);
+    }
+    while (Nhi - Nlo > 10) {
+        N = 0.5*(Nlo + Nhi);
+        if (index->data.F32[N] < key) {
+            Nlo = N;
+        } else {
+            Nhi = N + 1;
+        }
+    }
+    // at this point, index[Nhi] >= key > index[Nlo]
+    N = Nhi;
+    while ((index->data.F32[N] >= key) && (N > Nlo)) {
+        N--;
+    }
+    return (N);
+}
+
+// return the last entry below or first entry above key value (reverse sorted input)
+int psVectorBracketDescend(const psVector *index, psF32 key, bool above)
+{
+
+    int N;
+    int Nhi = 0;
+    int Nlo = index->n;
+
+    if (above) {
+        while (Nlo - Nhi > 10) {
+            N = 0.5*(Nhi + Nlo);
+            if (index->data.F32[N] < key) {
+                Nlo = N;
+            } else {
+                Nhi = N - 1;
+            }
+        }
+        // at this point, index[Nhi] >= key > index[Nlo]
+        N = Nhi;
+        while ((index->data.F32[N] >= key) && (N < Nlo)) {
+            N++;
+        }
+        return (N);
+    }
+    while (Nlo - Nhi > 10) {
+        N = 0.5*(Nhi + Nlo);
+        if (index->data.F32[N] > key) {
+            Nhi = N;
+        } else {
+            Nlo = N + 1;
+        }
+    }
+    // at this point, index[Nhi] > key >= index[Nlo]
+    N = Nlo;
+    while ((index->data.F32[N] <= key) && (N > Nhi)) {
+        N--;
+    }
+    return (N);
+}
+
+// search for the bins bounding key in index, interpolate the corresponding values
+psF32 psVectorInterpolate(const psVector *index, const psVector *value, psF32 key)
+{
+
+    int n0 = 0;
+    int n1 = 0;
+
+    // extrapolate at ends
+    if (key < index->data.F32[0]) {
+        n0 = 0;
+        n1 = 1;
+    }
+
+    // extrapolate at ends
+    if (key > index->data.F32[index->n-1]) {
+        n0 = index->n-2;
+        n1 = index->n-1;
+    }
+
+    if (n1 == 0) {
+        n0 = psVectorBracket (index, key, FALSE);
+        n1 = n0 + 1;
+    }
+
+    if (n0 == index->n-1) {
+        n1 = n0;
+        n0 = n1 - 1;
+    }
+
+    float dy = value->data.F32[n1] - value->data.F32[n0];
+    float dx = index->data.F32[n1] - index->data.F32[n0];
+    float dX = key - index->data.F32[n0];
+    float dY = dX * (dy/dx);
+    float result = value->data.F32[n0] + dY;
+    return result;
+}
+
Index: /tags/RHL_v1_1/psModules/src/objects/psVectorBracket.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/objects/psVectorBracket.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/objects/psVectorBracket.h	(revision 9533)
@@ -0,0 +1,11 @@
+
+# ifndef PS_VECTOR_BRACKET_H
+# define PS_VECTOR_BRACKET_H
+
+#include <pslib.h>
+
+int psVectorBracket(const psVector *index, psF32 key, bool above);
+int psVectorBracketDescend(const psVector *index, psF32 key, bool above);
+psF32 psVectorInterpolate(const psVector *index, const psVector *value, psF32 key);
+
+# endif /* PS_VECTOR_BRACKET_H */
Index: /tags/RHL_v1_1/psModules/src/parseErrorCodes.pl
===================================================================
--- /tags/RHL_v1_1/psModules/src/parseErrorCodes.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/parseErrorCodes.pl	(revision 9533)
@@ -0,0 +1,97 @@
+#!/usr/bin/perl
+
+# Provides functions for handling long command line options
+use Getopt::Long;
+
+my @ErrorCodes        = ();
+my @ErrorDescriptions = ();
+
+my $data = "psErrorCodes.dat";
+
+# Assign variables based on the presence of command line options to the script
+GetOptions(
+    "data=s"  => \$data,
+    "verbose" => \$verbose,
+    "help"    => \$help
+);
+
+if ($help) {
+    print "Usage: parseErrorCodes ", "[--data=$data] ", "[--help] ",
+      "[--verbose] filename [filename ...]\n\n";
+    exit(0);
+}
+
+print "Using data file '$data'\n" if $verbose;
+unless ( open( DATAFILE, "<", $data ) ) {
+    die "Can not open data file $data.";
+}
+
+print "Datafile:\n" if $verbose;
+while (<DATAFILE>) {
+    chop;
+    if (/^\s*\#/) {
+        print "C $_\n" if $verbose;
+    }
+    else {
+        if (/^\s*(\w+)\s+([\%\w].*)/) {
+            my $ErrorCode        = $1;
+            my $ErrorDescription = $2;
+            print "  $ErrorCode: '$ErrorDescription'\n" if $verbose;
+            push( @ErrorCodes,        $ErrorCode );
+            push( @ErrorDescriptions, $ErrorDescription );
+        } else {
+            print "I $_\n" if $verbose;
+        }
+    }
+}
+close(DATAFILE);
+
+my $found = $#ErrorCodes + 1;
+print "\nFound $found error codes.\n" if $verbose;
+
+foreach (@ARGV) {
+    my $filename = $_;
+    my @result   = ();
+
+    die "Failed to open input file"
+      if !open( INFILE, "<", $filename );
+
+    print "\nOutput File:\n" if $verbose;
+    while (<INFILE>) {
+        chop;
+        push( @result, $_ );
+        if (/^\s*\/\/~Start(.*)$/) {
+            $line = $1;
+            for ( $n = 0 ; $n < $found ; $n++ ) {
+                $_ = $line;
+                s/\$1/$ErrorCodes[$n]/g;
+                s/\$2/$ErrorDescriptions[$n]/g;
+                s/\$n/$n/g;
+                push( @result, $_ );
+                print "$_\n" if $verbose;
+            }
+
+            $break = 0;
+            while ( ( $break == 0 ) && ( $_ = <INFILE> ) ) {
+                if (/^\s*\/\/~End/) {
+                    $break = 1;
+                }
+            }
+            chop;
+            push( @result, $_ );
+        }
+    }
+
+    close(INFILE);
+
+    die "Failed to overwrite input file"
+      if !open( OUTFILE, ">", $filename );
+
+    foreach (@result) {
+        print OUTFILE "$_\n";
+        print "$_\n" if $verbose;
+    }
+
+    close(OUTFILE);
+
+}
Index: /tags/RHL_v1_1/psModules/src/psErrorCodes.dat
===================================================================
--- /tags/RHL_v1_1/psModules/src/psErrorCodes.dat	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/psErrorCodes.dat	(revision 9533)
@@ -0,0 +1,18 @@
+#
+#  This file is used to generate psErrorCode.h content
+#
+# Format:
+#    ERROR_CLASS_NAME    ERROR_CLASS_DESCRIPTION
+#
+# Note: the ERROR_CLASS_NAME will appear in the code with a "PS_ERR_" prefix
+#
+UNKNOWN                        unknown error
+IO                             I/O error
+LOCATION_INVALID               specified location is unknown
+MEMORY_CORRUPTION              memory corruption detected
+MEMORY_DEREF_USAGE             dereferenced memory still used
+BAD_PARAMETER_VALUE            parameter is out-of-range
+BAD_PARAMETER_TYPE             parameter is of unsupported type
+BAD_PARAMETER_NULL             parameter is null
+BAD_PARAMETER_SIZE             size of parameter's data is outside of acceptable range.
+UNEXPECTED_NULL                unexpected NULL found
Index: /tags/RHL_v1_1/psModules/src/psmodules.h
===================================================================
--- /tags/RHL_v1_1/psModules/src/psmodules.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/src/psmodules.h	(revision 9533)
@@ -0,0 +1,77 @@
+#ifndef PS_MODULES_H
+#define PS_MODULES_H
+
+#include <pslib.h>
+
+// the following headers are from psModule:config
+#include <pmConfig.h>
+
+// the following headers are from psModule:concepts
+#include <pmConcepts.h>
+#include <pmConceptsRead.h>
+#include <pmConceptsStandard.h>
+#include <pmConceptsWrite.h>
+#include <pmConceptsPhotcode.h>
+
+// the following headers are from psModule:camera
+#include <pmHDU.h>
+#include <pmHDUUtils.h>
+#include <pmHDUGenerate.h>
+#include <pmFPA.h>
+#include <pmFPAview.h>
+#include <pmFPAfile.h>
+#include <pmFPAfileDefine.h>
+#include <pmFPAfileFitsIO.h>
+#include <pmFPAfileIO.h>
+#include <pmFPARead.h>
+#include <pmFPAConstruct.h>
+#include <pmFPACopy.h>
+#include <pmFPAHeader.h>
+#include <pmFPAMaskWeight.h>
+#include <pmFPAMosaic.h>
+#include <pmFPARead.h>
+#include <pmFPAWrite.h>
+#include <pmFPA_JPEG.h>
+
+// the following headers are from psModule:detrend
+#include <pmFlatField.h>
+#include <pmFlatFieldErrors.h>
+#include <pmFlatNormalize.h>
+#include <pmFringeStats.h>
+#include <pmMaskBadPixels.h>
+#include <pmMaskBadPixelsErrors.h>
+#include <pmNonLinear.h>
+#include <pmSubtractBias.h>
+#include <pmDetrendDB.h>
+#include <pmShutterCorrection.h>
+#include <psPipe.h>
+#include <psIOBuffer.h>
+// #include <pmSubtractSky.h>
+
+// the following headers are from psModule:astrom
+#include <pmAstrometryObjects.h>
+#include <pmAstrometryDistortion.h>
+
+// the following headers are from psModule:imcombine
+#include <pmImageCombine.h>
+#include <pmImageSubtract.h>
+#include <pmReadoutCombine.h>
+
+// the following headers are from psModule:objects
+#include <pmPeaks.h>
+#include <pmMoments.h>
+#include <pmModel.h>
+#include <pmSource.h>
+#include <pmSourceIO.h>
+#include <pmSourceSky.h>
+#include <pmSourceFitModel.h>
+#include <pmSourceContour.h>
+#include <pmGrowthCurve.h>
+#include <pmPSF.h>
+#include <pmPSF_IO.h>
+#include <pmPSFtry.h>
+#include <pmModelGroup.h>
+#include <pmSourcePhotometry.h>
+#include <psVectorBracket.h>
+
+#endif
Index: /tags/RHL_v1_1/psModules/templates/c
===================================================================
--- /tags/RHL_v1_1/psModules/templates/c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/templates/c	(revision 9533)
@@ -0,0 +1,11 @@
+/** @file  filename.c
+ *
+ *  @brief Contains the definitions for ...
+ *
+ *  @author Robert DeSonia, MHPCC
+ *
+ *  @version $Revision: 1.1.1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2004-09-10 19:01:33 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
Index: /tags/RHL_v1_1/psModules/templates/h
===================================================================
--- /tags/RHL_v1_1/psModules/templates/h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/templates/h	(revision 9533)
@@ -0,0 +1,13 @@
+/** @file  filename.h
+ *
+ *  @brief Contains the definitions for ...
+ *
+ *  Detailed Description goes here.
+ *
+ *  @author Robert DeSonia, MHPCC
+ *
+ *  @version $Revision: 1.1.1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2004-09-10 19:01:33 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
Index: /tags/RHL_v1_1/psModules/test/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/.cvsignore	(revision 9533)
@@ -0,0 +1,15 @@
+.deps
+.libs
+Makefile
+tst_pmFlatField
+tst_pmMaskBadPixels
+tst_pmNonLinear
+tst_pmSubtractBias
+temp
+Makefile.in
+tst_pmObjects01
+tst_pmReadoutCombine
+tst_pmSubtractSky
+tst_pmImageSubtract
+tst_pmImageCombine
+tst_pmAstrometry
Index: /tags/RHL_v1_1/psModules/test/FullUnitTest
===================================================================
--- /tags/RHL_v1_1/psModules/test/FullUnitTest	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/FullUnitTest	(revision 9533)
@@ -0,0 +1,360 @@
+#!/usr/bin/perl
+#
+#  This is a perl script test harness which will recursively search the
+#  directory tree looking for files named UnitTest and then execute
+#  the script
+#
+#  SYNOSIS :  FullUnitTest options arguements
+#
+#       where  options =
+#             --verbose     Display extra information to user
+#             --noverbose   Don't display extra information to user
+#             --recursiv e  Recursively run tests in directory tree
+#             --norecursive Test only the specified or current directory
+#             --silent      Don't display any information to user
+#             --nosilent    Display progress of script to user
+#
+#              arguements = directory(ies) to perform tests
+#
+#  RETURN : integer number of tests which failed
+#
+#  $Revision: 1.3 $  $Name: not supported by cvs2svn $
+#  $Date: 2005-04-12 21:51:00 $
+#
+#  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+#
+##############################################################################
+
+use FindBin qw($Bin);
+
+$runTest = "$Bin/runTest";
+
+# Provide functions for determining the pathname of current working directory
+use Cwd;
+
+# Provides functions for handling psS64 command line options
+use Getopt::Long;
+
+# Assign variables based on the presence of command line options to the script
+# The ! option allows for --nooption to be set to zero
+# (e.g. --noverbose --recursive causes $verbose=0 and $recursive=1)
+GetOptions(
+    "verbose!"   => \$verbose,
+    "recursive!" => \$recursive,
+    "silent!"    => \$silent,
+    "clean!"     => \$clean
+);
+
+# Check if both silent and verbose options are set and if so stop the script
+die "Can't specify both verbose and silent options." if ( $verbose && $silent );
+
+# Set up the PSLIB_ROOT environment variable if the user doesn't have
+if ( !$ENV{'PSLIB_ROOT'} ) {
+
+    # Use the directory directly above where FullUnitTest script resides
+    $PSLIB_ROOT = `cd ..;pwd`;
+
+    # Remove newline for the end of path returned
+    chomp($PSLIB_ROOT);
+
+    # Set the environment variable
+    $ENV{'PSLIB_ROOT'} = $PSLIB_ROOT;
+    print("PSLIB_ROOT not found: set to $PSLIB_ROOT.\n") if $verbose;
+}
+
+# add PSLIB_ROOT/lib to LD_LIBRARY_PATH and DYLD_LIBRARY_PATH environment
+# variables
+$ENV{'LD_LIBRARY_PATH'}   = "$ENV{'PSLIB_ROOT'}/src/.libs:$ENV{'LD_LIBRARY_PATH'}";
+$ENV{'DYLD_LIBRARY_PATH'} = "$ENV{'PSLIB_ROOT'}/src/.libs:$ENV{'DYLD_LIBRARY_PATH'}";
+
+# Initialize variables for counting the makes and test failures and the
+# total makes and tests performed
+$makeFailCount       = 0;
+$testpointFailCount  = 0;
+$testDriverFailCount = 0;
+$totalTestpoints     = 0;
+$totalTestDrivers    = 0;
+$totalMakes          = 0;
+
+# Initialize variable indicating how many arguements were passed
+$args = 0;
+
+# Loop through all the arguements passed to the script
+foreach (@ARGV) {
+
+    # Remove newline if there is one
+    chomp;
+
+    # Increment number of arguements found
+    $args++;
+
+    # Set variable to current working directory
+    $cwd = cwd;
+
+    # Change directory to the directory of the arguement
+    chdir($_);
+
+    # Check for the recursive option
+    if ($recursive) {
+
+        # Invoke subroutine to go to the lowest directory in tree
+        # starting at the specified directory
+        &worm($_);
+    }
+    else {
+
+        # Invoke subroutines to run the test at the specified directory
+        &makeTestDrivers($_);
+        &executeTestDrivers($_);
+    }
+
+    # Change directory back to the directory where FullUnitTest was invoked
+    chdir($cwd);
+}
+
+# Check if there were no arguements specified
+if ( $args == 0 ) {
+
+    # Display message to user that all directories under the current will be
+    # tested
+    print("Recursively testing current directory tree.\n") if $verbose;
+
+    # Invoke subroutine to go to recursively test each directory in tree
+    &worm( $ENV{"PWD"} );
+}
+
+# Check if there were any failures during make or testing
+if ( $makeFailCount > 0 || $testDriverFailCount > 0 ) {
+
+    # Display summary of failures
+    print("\nMake Failures = $makeFailCount out of $totalMakes");
+    print(
+"\nTest Driver Failures = $testDriverFailCount out of $totalTestDrivers\n"
+    );
+    print( "\nMakes that failed:\n  " . join( "\n  ", @makesFailed ) . "\n" )
+      if $makeFailCount;
+    print( "\nTests that failed:\n  " . join( "\n  ", @testsFailed ) . "\n" )
+      if $testDriverFailCount;
+}
+else {
+
+    # Display message of all makes and tests pass to user if silent option
+    # not specified
+    print(
+"\nAll $totalTestDrivers Test Drivers Passed with $totalTestpoints Testpoints.\n"
+      )
+      if ( !$silent );
+}
+
+# Exit with the number of tests that failed
+exit($testDriverFailCount);
+
+################################################################################
+#
+# SUBROUTINE: worm
+#
+#     Description:  This subroutine will perform the necessary unit tests be
+#                   calling makeTestDrivers and executeTestDrivers  subroutines
+#                   and then check each subdirectory below the base directory
+#                   recursively.
+#
+#     Parameter(s):  base directory to start testing
+#
+#     Return:  None
+#
+################################################################################
+
+sub worm {
+
+    # Assign local variable to input parameter
+    local ($base_dir) = @_;
+    local ( @files, $i );
+
+    # Invoke subroutine to make test driver in the base directory
+    &makeTestDrivers($base_dir);
+
+    # Invoke subroutine to execute tests in the base directory
+    &executeTestDrivers($base_dir);
+
+    # Create array of entries found in directory
+    @files = <*>;
+
+    # Loop through the file list looking for another directory that is not
+    # labelled CVS and recursively invoke subroutine worm
+    $i = 0;
+    while ( $files[$i] ) {
+
+        # Check for directory and directory not labelled CVS
+        if (   -d $files[$i]
+            && ( $files[$i] ne "CVS" )
+            && ( $files[$i] ne "temp" )
+            && ( $files[$i] ne "verified" ) )
+        {
+
+            # Change current directory to directory found
+            chdir( $files[$i] );
+
+            # Invoke subroutine worm again
+            &worm( $files[$i] );
+
+            # Change current directory back to parent
+            chdir("..");
+        }
+
+        # Increment file list index
+        $i++;
+    }
+}
+
+################################################################################
+#
+#  SUBROUTINE: makeTestDrivers
+#
+#      Description:  This subroutine will perform a make for all the necessary
+#                    test drivers.    This will occur in the specified
+#                    base directory which is passed as the only parameter.
+#
+#      Parameter(s):  base directory where make and test drivers are located
+#
+#      Return:  None
+#
+################################################################################
+
+sub makeTestDrivers {
+    local ($base_dir) = @_;
+    local ($pwd);
+
+    # Set variable pwd to current working directory
+    $pwd = cwd;
+
+    # Display message to user in which directory testing is taken place only
+    # if the silent command option was not specified
+    print("---- Entering $pwd ----\n") if ( !$silent );
+
+    # Check for the existence of a file name Makefile in current directory
+    if ( -e "Makefile" ) {
+
+        # Increment the total number of makes executed
+        $totalMakes++;
+
+        if ($clean) {
+
+            # Execute the make clean
+            `make clean`;
+
+        }
+
+        # Execute the make and save results
+        $_ = join( "\n|| ", split( "\n", "\n" . `make tests` ) );
+
+        # Check the output of make for return value != 0 or any of the
+        # following words: FAILED, FAULT, ERROR, Not found, SIGNAL
+        if ( $? != 0 ) {
+
+            # Display the errored output of make if silent option not enabled
+            print("$_\n") if ( !$silent );
+
+            # Display the make failed in the current directory
+            print("\nMake for $pwd Failed\n");
+
+            # Increment the number of makes that have failed
+            $makeFailCount++;
+
+            # Push the current working directory onto the list of failed
+            # make directories list
+            push( @makesFailed, $pwd );
+        }
+        else {
+
+            # Display the results of the successful make if verbose option set
+            print("$_\n") if $verbose;
+
+            # Display the make was successful if silent option not set
+            print("\nMake successful.\n") if ( !$silent );
+        }
+    }
+}
+
+################################################################################
+#
+#  SUBROUTINE: executeTestDrivers
+#
+#      Description:  This subroutine will execute all the necessary
+#                    test drivers.    This will occur in the specified
+#                    base directory which is passed as the only parameter.
+#
+#      Parameter(s):  base directory where make and test drivers are located
+#
+################################################################################
+
+sub executeTestDrivers {
+    local ($base_dir) = @_;
+    local ( @files, $j );
+    local ($pwd);
+    local ($exitValue) = 0;
+
+    # Set variable to pwd to current test directory
+    $pwd = cwd;
+
+    # Create a list of all elements in base directory
+    @files = <*>;
+
+    # Loop through all the items in the files array
+    $initialTest = 0;
+    $j           = 0;
+    while ( $files[$j] ) {
+
+        # Check that the item is not a directory and is executable and
+        # conforms to the test driver naming convention TST
+        if (   !( -d $files[$j] )
+            && ( -x $files[$j] )
+            && ( $files[$j] =~ /^TST/i || $files[$j] =~ /^ATST/i ) )
+        {
+
+            # Increment total number of test drivers executed
+            $totalTestDrivers++;
+
+            # Display message to user of which test driver is being executed
+            print("--- Executing test driver $files[$j]\n") if ( !$silent );
+
+            # Execute the test driver
+            if ($silent) {
+                system("$runTest --quiet ./$files[$j]");
+            } elsif ($verbose) {
+                system("$runTest --printpassfail ./$files[$j]");
+            } else {
+                system("$runTest ./$files[$j]");
+            }
+            $retVal = $?;
+
+            # Count testpoints
+            $testPattern = "\"\\*\\*\\*\\* TESTPOINT \\*\\*\\*\\*\"";
+            $totalPoints = `grep -c $testPattern temp/$files[$j].stdout`;
+            $totalPoints += `grep -c $testPattern temp/$files[$j].stderr`;
+            $failPoints =
+              `grep "> TESTPOINT FAILED" temp/$files[$j].stdout | wc -l`;
+            $failPoints +=
+              `grep "> TESTPOINT FAILED" temp/$files[$j].stderr | wc -l`;
+            $testpointFailCount += $failPoints;
+            $totalTestpoints    += $totalPoints;
+
+            # Check result of test driver
+            if ( $retVal != 0 )
+            {
+
+                # Display test driver failed
+                $failPoints++;
+                print(
+                    "Test driver: $files[$j] Failed (Return value $retVal).\n");
+
+                # Increment the total number of test failed
+                $testDriverFailCount++;
+
+                # Push the current working directory on the list of failed
+                push( @testsFailed, $pwd . "/" . $files[$j] );
+            }
+        }
+        $j++;
+    }
+}
+
Index: /tags/RHL_v1_1/psModules/test/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/Makefile.am	(revision 9533)
@@ -0,0 +1,9 @@
+SUBDIRS = tap $(SRCDIRS)
+
+TESTS = test.pl
+
+EXTRA_DIST = test.pl
+
+CLEANFILES = core core.* *~
+
+test: check
Index: /tags/RHL_v1_1/psModules/test/astrom/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/astrom/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/astrom/.cvsignore	(revision 9533)
@@ -0,0 +1,7 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmAstrometry
+tst_pmAstrometry01
Index: /tags/RHL_v1_1/psModules/test/astrom/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/astrom/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/astrom/Makefile.am	(revision 9533)
@@ -0,0 +1,22 @@
+AM_LDFLAGS = -L$(top_builddir)/src -lpsmodules $(PSMODULES_LIBS)
+AM_CFLAGS  = @AM_CFLAGS@ $(PSMODULES_CFLAGS) $(SRCINC)
+AM_CPPFLAGS = $(SRCINC) $(PSLIB_CFLAGS)
+
+TESTS = \
+    tst_pmAstrometry \
+    tst_pmAstrometry01
+
+tst_pmAstrometry_SOURCES = tst_pmAstrometry.c
+tst_pmAstrometry01_SOURCES = tst_pmAstrometry01.c
+
+check_PROGRAMS = $(TESTS)
+
+TESTS_ENVIRONMENT = perl $(top_srcdir)/test/runTest --verified=$(srcdir)/verified
+
+tests: $(TESTS)
+
+EXTRA_DIST = verified
+
+CLEANFILES = $(TESTS) temp/* *~
+
+test: check
Index: /tags/RHL_v1_1/psModules/test/astrom/tst_pmAstrometry.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/astrom/tst_pmAstrometry.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/astrom/tst_pmAstrometry.c	(revision 9533)
@@ -0,0 +1,615 @@
+/** @file  tst_pmAstrometry.c
+ *
+ *  @brief Contains the tests: pmAstrometry.[ch].  The pmxxxAlloc()
+ *  and psFree() functionality are used here.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  XXX: Untested: pmFPACheckParents()
+ *  XXX: Create the pmHDU alloc/free function, test them here
+ *
+ *  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-01-26 21:10:51 $
+ *
+ *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#include "config.h"
+#include <math.h>
+#include <string.h>
+#include "psTest.h"
+#include "pslib_strict.h"
+#include "pmAstrometry.h"
+
+static psS32 testFPAAlloc(void);
+static psS32 testChipAlloc(void);
+static psS32 testCellAlloc(void);
+static psS32 testReadoutAlloc(void);
+
+testDescription tests[] = {
+                              {testFPAAlloc,739,"pmFPAAlloc",0,false},
+                              {testChipAlloc,740,"pmChipAlloc",0,false},
+                              {testCellAlloc,741,"pmCellAlloc",0,false},
+                              {testReadoutAlloc,742,"pmReadoutAlloc",0,false},
+                              {NULL}
+                          };
+
+#define CHIP_ALLOC_NAME "ChipName"
+#define CELL_ALLOC_NAME "CellName"
+#define MISC_NUM 32
+#define MISC_NAME "META00"
+#define MISC_NAME2 "META01"
+#define NUM_BIAS_DATA 10
+#define TEST_NUM_ROWS 32
+#define TEST_NUM_COLS 32
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.  We do this to ensure that the data is
+later being psFree()'ed correctly.
+ *****************************************************************************/
+pmFPA *generateSimpleFPA()
+{
+    psBool rc;
+    pmFPA* fpa = pmFPAAlloc(psMetadataAlloc());
+
+    if (fpa == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc() returned a NULL.");
+        return(NULL);
+    }
+
+    //
+    // Test and create camera metadata.
+    //
+    if (fpa->camera == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: fpa->camera is NULL.");
+        psFree(fpa);
+        return(NULL);
+    } else {
+        rc = psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+        if (rc == false) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not add metadata to fpa->camera.");
+            psFree(fpa);
+            return(NULL);
+        }
+        psS32 tmpS32 = psMetadataLookupS32(&rc, fpa->camera, MISC_NAME);
+        if ((rc == false) || (tmpS32 != MISC_NUM)) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not read metadata from fpa->camera.");
+            psFree(fpa);
+            return(NULL);
+        }
+    }
+
+    //
+    // Create various transforms and projections.
+    //
+    fpa->fromTangentPlane = PS_CREATE_4D_IDENTITY_PLANE_DISTORT();
+    fpa->toTangentPlane = PS_CREATE_4D_IDENTITY_PLANE_DISTORT();
+    fpa->projection = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+
+    //
+    // Ensure fpa concepts metadata was allocated properly.
+    //
+    if (fpa->concepts == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set fpa->concepts.");
+        psFree(fpa);
+        return(NULL);
+    } else {
+        rc = psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+        if (rc == false) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not add data to fpa->concepts.");
+            psFree(fpa);
+            return(NULL);
+        }
+        psS32 tmpS32 = psMetadataLookupS32(&rc, fpa->concepts, MISC_NAME);
+        if ((rc == false) || (tmpS32 != MISC_NUM)) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not read metadata from fpa->concepts.");
+            psFree(fpa);
+            return(NULL);
+        }
+    }
+
+    //
+    // Create ->analysis metadata.
+    //
+    fpa->analysis = psMetadataAlloc();
+    rc = psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    //
+    // We test the fpa->chips array later.
+    //
+
+    //
+    // How to test the p_pmHDU *hdu member?
+    //
+
+    //
+    // Create ->phu metadata.
+    //
+    fpa->phu = psMetadataAlloc();
+    rc = psMetadataAddS32(fpa->phu, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    return(fpa);
+}
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetLevel(PS_LOG_INFO);
+    psLogSetFormat("HLNM");
+
+    return ! runTestSuite(stderr,"pmAstrometry",tests,argc,argv);
+}
+
+/******************************************************************************
+testFPAAlloc()
+    1: We ensure that pmFPAAlloc() properly allocates a pmFPA struct.
+    2: We populate the members with real data to ensure they are being
+       free'ed correctly.
+ *****************************************************************************/
+static psS32 testFPAAlloc(void)
+{
+    psMetadata *camera = psMetadataAlloc();
+    pmFPA* fpa = pmFPAAlloc(camera);
+
+    if (fpa == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc() returned a NULL.");
+        return 1;
+    }
+
+    if (fpa->fromTangentPlane != NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->fromTangentPlane to NULL.");
+        return 2;
+    }
+
+    if (fpa->toTangentPlane != NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->toTangentPlane to NULL.");
+        return 3;
+    }
+    if (fpa->projection != NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->projection to NULL.");
+        return 4;
+    }
+
+    if (fpa->concepts == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->concepts.");
+        return 5;
+    }
+
+    if (fpa->analysis != NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->analysis to NULL.");
+        return 6;
+    }
+
+    if (fpa->camera != camera) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->camera.");
+        return 7;
+    }
+
+    if (fpa->chips == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->chips.");
+        return 8;
+    }
+
+    if (fpa->hdu != NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->hdu to NULL.");
+        return 9;
+    }
+    psFree(fpa);
+
+    //
+    // Populate the pmFPA struct with real data to ensure they were
+    // psFree()'ed correctly.
+    //
+    fpa = generateSimpleFPA();
+    if (fpa == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: generateSimpleFPA() returned NULL.");
+        return(15);
+    }
+    psFree(fpa);
+
+    return(0);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.  We do this to ensure that the data is
+later being psFree()'ed correctly.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    psBool rc;
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    if (chip == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmChipAlloc returned a NULL.");
+        return(NULL);
+    }
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    //
+    // We already ensured that chip->concepts was working properly.
+    //
+
+    //
+    // Create ->analysis metadata.
+    //
+    chip->analysis = psMetadataAlloc();
+    rc = psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    //
+    // We test the chip->cells array later.
+    //
+
+    //
+    // How to test the p_pmHDU *hdu member?
+    //
+
+    return(chip);
+}
+
+static psS32 testChipAlloc(void)
+{
+    pmFPA* fpa = generateSimpleFPA();
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    if (chip == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmChipAlloc returned a NULL.");
+        return 1;
+    }
+
+    if (chip->col0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->col0 set improperly.\n");
+        return 5;
+    }
+
+    if (chip->row0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->row0 set improperly.\n");
+        return 6;
+    }
+
+    if (chip->toFPA != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->toChip set improperly.\n");
+        return 7;
+    }
+
+    if (chip->fromFPA != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->toFPA set improperly.\n");
+        return 8;
+    }
+
+    if (chip->concepts == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->concepts set improperly.\n");
+        return 21;
+    } else {
+        psMetadataItem *tmpMeta = psMetadataLookup(chip->concepts, "CHIP.NAME");
+        if (0 != strcmp((char *) tmpMeta->data.V, CHIP_ALLOC_NAME)) {
+            psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: The metadata was set improperly.\n");
+            return (32);
+        }
+        // XXX: Code a test to ensure the metadata has the correct type
+    }
+
+    if (chip->analysis != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->analysis set improperly.\n");
+        return 10;
+    }
+
+    if (chip->cells == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->cells set improperly.\n");
+        return 22;
+    }
+
+    if (chip->parent != fpa) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->parent set improperly.\n");
+        return 23;
+    }
+
+    if (chip->valid != false) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->valid set improperly.\n");
+        return 24;
+    }
+
+    if (chip->hdu != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->hdu set improperly.\n");
+        return 25;
+    }
+    psFree(fpa);
+
+    //
+    // Populate the pmChip struct with real data to ensure they were
+    // psFree()'ed correctly.
+    //
+    fpa = generateSimpleFPA();
+    chip = generateSimpleChip(fpa);
+    psFree(fpa);
+
+    return(0);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.  We do this to ensure that the data is
+later being psFree()'ed correctly.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmFPA *fpa, pmChip *chip)
+{
+    psBool rc;
+    pmCell *cell = pmCellAlloc(chip, (psMetadata *) fpa->camera, CELL_ALLOC_NAME);
+    if (cell == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmCellAlloc returned a NULL.");
+        return(NULL);
+    }
+    cell->toChip = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    cell->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    cell->toSky = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    //
+    // We already ensured that cell->concepts was working properly.
+    //
+
+    //
+    // Test camera metadata.
+    //
+    if (cell->camera == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->camera is NULL.");
+        psFree(fpa);
+        return(NULL);
+    } else {
+        rc = psMetadataAddS32((psMetadata *) cell->camera, PS_LIST_HEAD, MISC_NAME2, 0, NULL, MISC_NUM);
+        if (rc == false) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not add metadata to cell->camera.");
+            psFree(fpa);
+            return(NULL);
+        }
+        psS32 tmpS32 = psMetadataLookupS32(&rc, cell->camera, MISC_NAME2);
+        if ((rc == false) || (tmpS32 != MISC_NUM)) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not read metadata from cell->camera.");
+            psFree(fpa);
+            return(NULL);
+        }
+    }
+
+    //
+    // Create ->analysis metadata.
+    //
+    cell->analysis = psMetadataAlloc();
+    rc = psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    //
+    // We test the cell->readouts array later.
+    //
+
+    //
+    // How to test the p_pmHDU *hdu member?
+    //
+
+    return(cell);
+}
+
+static psS32 testCellAlloc(void)
+{
+    pmFPA* fpa = generateSimpleFPA();
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    pmCell *cell = pmCellAlloc(chip, (psMetadata *) fpa->camera, CELL_ALLOC_NAME);
+    if (cell == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmCellAlloc returned a NULL.n");
+        return 3;
+    }
+
+    if (cell->col0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->col0 set improperly.\n");
+        return 5;
+    }
+
+    if (cell->row0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->row0 set improperly.\n");
+        return 6;
+    }
+
+    if (cell->toChip != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->toChip set improperly.\n");
+        return 7;
+    }
+
+    if (cell->toFPA != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->toFPA set improperly.\n");
+        return 8;
+    }
+
+    if (cell->toSky != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->toSky set improperly.\n");
+        return 9;
+    }
+
+    if (cell->concepts == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->concepts set improperly.\n");
+        return 21;
+    } else {
+        psMetadataItem *tmpMeta = psMetadataLookup(cell->concepts, "CELL.NAME");
+        if (0 != strcmp((char *) tmpMeta->data.V, CELL_ALLOC_NAME)) {
+            psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: The metadata was set improperly.\n");
+            return (32);
+        }
+        // XXX: Code a test to ensure the metadata has the correct type
+    }
+
+    if (cell->camera != fpa->camera) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->camera set improperly.\n");
+        return 20;
+    }
+
+    if (cell->analysis != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->analysis set improperly.\n");
+        return 10;
+    }
+
+    if (cell->readouts == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->readouts set improperly.\n");
+        return 22;
+    }
+
+    if (cell->parent != chip) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->parent set improperly.\n");
+        return 23;
+    }
+
+    if (cell->valid != false) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->valid set improperly.\n");
+        return 24;
+    }
+
+    if (cell->hdu != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->hdu set improperly.\n");
+        return 27;
+    }
+    psFree(fpa);
+
+    //
+    // Populate the pmCell struct with real data to ensure they were
+    // psFree()'ed correctly.
+    //
+    fpa = generateSimpleFPA();
+    chip = generateSimpleChip(fpa);
+    cell = generateSimpleCell(fpa, chip);
+    psFree(fpa);
+
+    return(0);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.  We do this to ensure that the data is
+later being psFree()'ed correctly.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    psBool rc;
+    pmReadout *readout = pmReadoutAlloc(cell);
+    if (readout == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmReadoutAlloc returned a NULL.");
+        return(NULL);
+    }
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+
+    //
+    // Create a psList of bias data.
+    //
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        if (readout->bias == NULL) {
+            readout->bias = psListAlloc(tmpImage);
+        } else {
+            psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        }
+    }
+
+    //
+    // Test readout->analysis metadata.
+    //
+    if (readout->analysis == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: readout->analysis is NULL.");
+        psFree(fpa);
+        return(NULL);
+    } else {
+        rc = psMetadataAddS32((psMetadata *) readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+        if (rc == false) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not add metadata to readout->analysis.");
+            psFree(fpa);
+            return(NULL);
+        }
+    }
+
+    return(readout);
+}
+
+
+static psS32 testReadoutAlloc(void)
+{
+    pmFPA* fpa = generateSimpleFPA();
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    pmCell *cell = pmCellAlloc(chip, (psMetadata *) fpa->camera, CELL_ALLOC_NAME);
+    pmReadout *readout = pmReadoutAlloc(cell);
+    if (readout == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmReadoutAlloc returned a NULL.\n");
+        return 4;
+    }
+
+    if (readout->col0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->col0 set improperly.\n");
+        return 5;
+    }
+
+    if (readout->row0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->row0 set improperly.\n");
+        return 6;
+    }
+
+    if (readout->colBins != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->colBins set improperly.\n");
+        return 7;
+    }
+
+    if (readout->rowBins != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->colBins set improperly.\n");
+        return 8;
+    }
+
+    if (readout->image != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->image set improperly.\n");
+        return 10;
+    }
+
+    if (readout->mask != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->mask set improperly.\n");
+        return 12;
+    }
+
+    if (readout->weight != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->weight set improperly.\n");
+        return 14;
+    }
+
+    if (readout->bias != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->bias set improperly.\n");
+        return 16;
+    }
+
+    if (readout->analysis == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->analysis set improperly.\n");
+        return 18;
+    }
+
+    if (readout->parent != cell) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->parent set improperly.\n");
+        return 20;
+    }
+    psFree(fpa);
+
+    //
+    // Populate the pmReadout struct with real data to ensure they were
+    // psFree()'ed correctly.
+    //
+    fpa = generateSimpleFPA();
+    chip = generateSimpleChip(fpa);
+    cell = generateSimpleCell(fpa, chip);
+    readout = generateSimpleReadout(fpa, chip, cell);
+    psFree(fpa);
+
+    return(0);
+}
Index: /tags/RHL_v1_1/psModules/test/astrom/tst_pmAstrometry01.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/astrom/tst_pmAstrometry01.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/astrom/tst_pmAstrometry01.c	(revision 9533)
@@ -0,0 +1,675 @@
+/** @file  tst_psAstrometry01.c
+*
+*  @brief This code will test the pmFPA hierarchy transform code in psAstrometry.[ch]
+*
+*  @author GLG, MHPCC
+*
+*  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-03-06 22:53:47 $
+*
+* XXX: Add tests were the coordinate does not transform to any legitimate cell
+* or chip, or FPA, or whatever.
+*
+* XXX: For each function, add tests for bad input parameters, as well as failed transforms.
+*
+* XXX: Must test pmFPASelectChip() and pmFPAExcludeChip().
+*
+*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+*/
+#include "config.h"
+#include <math.h>
+#include <string.h>
+#include "psTest.h"
+#include "pslib_strict.h"
+#include "pmAstrometry.h"
+static psS32 test3( void );
+static psS32 test4( void );
+static psS32 test5( void );
+
+testDescription tests[] = {
+                              {test3, 666, "pmAstrometry focal plane transformations", 0, false},
+                              {test4, 667, "pmCheckParents()", 0, false},
+                              {test5, 668, "pmFPASelectChip() and pmFPAExcludeChip()", 0, false},
+                              {NULL}
+                          };
+
+psS32 currentId = 0;
+psS32 memLeaks = 0;
+psS32 main( psS32 argc, char* argv[] )
+{
+    psLogSetLevel( PS_LOG_INFO );
+    psLogSetFormat("HLNM");
+
+    return ( ! runTestSuite( stderr, "psAstrometry", tests, argc, argv ) );
+}
+
+#define PS_PERCENT_COMPARE(X, Y, PERCENT_FRACTION) (fabs((Y)-(X))/fabs(X) < (PERCENT_FRACTION))
+#define VERBOSE 0
+#define NUM_READOUTS 1
+#define READOUT_NUM_ROWS 10
+#define READOUT_NUM_COLS 10
+
+#define NUM_CELLS 4
+#define CELL_GAP 2
+#define CELL_WIDTH READOUT_NUM_COLS
+#define CELL_HEIGHT READOUT_NUM_ROWS
+#define CELL_MIN_X 0
+#define CELL_MAX_X CELL_HEIGHT
+#define CELL_MIN_Y 0
+#define CELL_MAX_Y CELL_WIDTH
+
+#define NUM_CHIPS 2
+#define CHIP_GAP 2
+#define CHIP_MIN_X 0
+#define CHIP_MAX_X CELL_HEIGHT
+#define CHIP_MIN_Y 0
+#define CHIP_MAX_Y ((NUM_CELLS * CELL_WIDTH) + ((NUM_CELLS) * CELL_GAP))
+#define CHIP_WIDTH CHIP_MAX_Y
+#define CHIP_HEIGHT CHIP_MAX_X
+
+#define NUM_FPAS 1
+#define FPA_MIN_X 0
+#define FPA_MAX_X CHIP_HEIGHT
+#define FPA_MIN_Y 0
+#define FPA_MAX_Y (((NUM_CHIPS) * CHIP_WIDTH) + (((NUM_CHIPS)-1) * CHIP_GAP))
+
+#define PROJECTION_SCALE_X 1.0
+#define PROJECTION_SCALE_Y 1.0
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+genSystem(): This routine will create a system of FPAs/Chips/Cells/Readouts.  For
+simplicity, an FPA is defined as a linear array of chips, and a chip is
+defined as a linear array of cells, both in the y (cols) direction.  The
+transforms between the various layers take into account the cell/chip and
+the boundaries between each.
+ *****************************************************************************/
+pmFPA *genSystem()
+{
+    //
+    // Create top pmFPA structure.
+    //
+    const psMetadata *camera = psMetadataAlloc();
+    pmFPA* myFPA = pmFPAAlloc(camera);
+    if (myFPA == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc() returned a NULL.\n");
+        return NULL;
+    }
+    myFPA->fromTangentPlane = PS_CREATE_4D_IDENTITY_PLANE_DISTORT();
+    myFPA->toTangentPlane = PS_CREATE_4D_IDENTITY_PLANE_DISTORT();
+    myFPA->projection = psProjectionAlloc(0.0,0.0,PROJECTION_SCALE_X,PROJECTION_SCALE_X,PS_PROJ_TAN);
+
+    myFPA->chips = psArrayRealloc(myFPA->chips, NUM_CHIPS);
+    for (psS32 chipID=0 ; chipID<NUM_CHIPS ; chipID++) {
+        pmChip *myChip = pmChipAlloc(myFPA, "ChipName");
+        if (myChip == NULL) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmChipAlloc() returned a NULL.\n");
+            return NULL;
+        }
+        myFPA->chips->data[chipID] = (psPtr *) myChip;
+        myChip->row0 = 0;
+        myChip->col0 = chipID * (CHIP_WIDTH + CHIP_GAP);
+
+        // We create the transforms between the chip and FPA.  The
+        // transform is a simple identity transform.
+        myChip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+        myChip->toFPA->y->coeff[0][0] = (psF64) myChip->col0;
+        myChip->toFPA->y->coeff[1][1] = 0.0;
+        myChip->toFPA->x->coeff[0][0] = (psF64) myChip->row0;
+        myChip->toFPA->x->coeff[1][1] = 0.0;
+
+        myChip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+        myChip->fromFPA->y->coeff[0][0] = (psF64) (- myChip->col0);
+        myChip->fromFPA->y->coeff[1][1] = 0.0;
+        myChip->fromFPA->x->coeff[0][0] = (psF64) (- myChip->row0);
+        myChip->fromFPA->x->coeff[1][1] = 0.0;
+
+        myChip->cells = psArrayRealloc(myChip->cells, NUM_CELLS);
+        for (psS32 cellID=0 ; cellID<NUM_CELLS ; cellID++) {
+            pmCell *myCell = pmCellAlloc(myChip, NULL, "CellName");
+            if (myCell == NULL) {
+                psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmCellAlloc() returned a NULL.\n");
+                return NULL;
+            }
+            myChip->cells->data[cellID] = (psPtr *) myCell;
+            myCell->row0 = 0;
+            myCell->col0 = cellID * (CELL_WIDTH + CELL_GAP);
+            myCell->toChip = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+            myCell->toChip->y->coeff[0][0] = (psF64) myCell->col0;
+            myCell->toChip->y->coeff[1][1] = 0.0;
+            myCell->toChip->x->coeff[0][0] = (psF64) myCell->row0;
+            myCell->toChip->x->coeff[1][1] = 0.0;
+
+            myCell->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+            myCell->toFPA->y->coeff[0][0] = (psF64) (myCell->col0 + myChip->col0);
+            myCell->toFPA->y->coeff[1][1] = 0.0;
+            myCell->toFPA->x->coeff[0][0] = (psF64) (myCell->row0 + myChip->row0);
+            myCell->toFPA->x->coeff[1][1] = 0.0;
+
+            myCell->toSky = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+            myCell->toSky->y->coeff[0][0] = myCell->toFPA->y->coeff[0][0];
+            myCell->toSky->y->coeff[1][1] = 0.0;
+            myCell->toSky->x->coeff[0][0] = myCell->toFPA->x->coeff[0][0];
+            myCell->toSky->x->coeff[1][1] = 0.0;
+
+            myCell->readouts = psArrayRealloc(myCell->readouts, NUM_READOUTS);
+            for (psS32 readoutID=0 ; readoutID<NUM_READOUTS ; readoutID++) {
+                pmReadout *myReadout = pmReadoutAlloc(myCell);
+                if (myReadout == NULL) {
+                    psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmReadoutAlloc() returned a NULL.\n");
+                    return NULL;
+                }
+                myCell->readouts->data[readoutID] = (psPtr *) myReadout;
+                myReadout->image = psImageAlloc(READOUT_NUM_COLS, READOUT_NUM_ROWS, PS_TYPE_F32);
+                for (psS32 row=0;row<READOUT_NUM_ROWS;row++) {
+                    for(psS32 col=0;col<READOUT_NUM_COLS;col++) {
+                        myReadout->image->data.F32[row][col] = (psF32) ((chipID * (CHIP_WIDTH + CHIP_GAP)) +
+                                                               (cellID * (CELL_WIDTH + CELL_GAP)));
+                    }
+                }
+                myReadout->row0 = myCell->row0;
+                myReadout->col0 = myCell->col0;
+                myReadout->rowBins = 0;
+                myReadout->colBins = 0;
+            }
+            if (VERBOSE) {
+                printf("\n\n\n\nFor chip %d cell %d the cell->toFPA transform is:\n", chipID, cellID);
+                PS_PRINT_PLANE_TRANSFORM(myCell->toFPA);
+                printf("\n\n\n\nFor chip %d cell %d the cell->toChip transform is:\n", chipID, cellID);
+                PS_PRINT_PLANE_TRANSFORM(myCell->toChip);
+                printf("\n\n\n\nFor chip %d cell %d the cell->toSky transform is:\n", chipID, cellID);
+                PS_PRINT_PLANE_TRANSFORM(myCell->toSky);
+            }
+        }
+        if (VERBOSE) {
+            printf("\n\n\n\nFor chip %d the chip->toFPA transform is:\n", chipID);
+            PS_PRINT_PLANE_TRANSFORM(myChip->toFPA);
+            printf("\n\n\n\nFor chip %d the chip->fromFPA transform is:\n", chipID);
+            PS_PRINT_PLANE_TRANSFORM(myChip->fromFPA);
+        }
+    }
+
+    return(myFPA);
+}
+
+
+/******************************************************************************
+This routine tests many Astrometry functions.  It loops through all valid
+pixels of all cells of all chips and computes the corresponding (x,y)
+coordinates in the FPA plane.  It then calls the pmChipInFPA() then
+pmCellInFPA() with the FPA coordinate and determines the chip/cell that that
+coordinate corresponds to.  Following that it does a variety of tests on the
+various functions that tharnsform coordinates within the pmFPA hierarchy.
+ 
+List of tested functions:
+    pmCellInFPA()  yes
+    pmChipInFPA()  yes
+    pmCellInChip()  yes
+ 
+    pmCoordCellToFPA()  yes
+    pmCoordChipToFPA()  yes
+    pmCoordFPAToChip()  yes
+    pmCoordCellToChip()  yes
+    pmCoordChipToCell()  yes
+ 
+    pmCoordFPAToTP()  yes
+    pmCoordTPToFPA()  yes
+ 
+    pmCoordTPToSky()  yes
+    pmCoordSkyToTP()  yes
+    pmCoordSkyToCell()  yes
+    pmCoordCellToSky()  yes
+    pmCoordCellToSkyQuick() yes
+    pmCoordSkyToCellQuick() yes
+ *****************************************************************************/
+psS32 test3( void )
+{
+    psS32 x;
+    psS32 y;
+    psPlane fpaCoord;
+    pmFPA *myFPA = genSystem();
+    pmCell *myCell = NULL;
+    psPlane chipCoord;
+    psPlane cellCoord;
+    psPlane testCoord;
+    psSphere *skyCoord = psSphereAlloc();
+    // XXX: This code causes a seg fault.
+    //    psSphere skyTmp;
+    //    psMemCheckType(PS_DATA_SPHERE, &skyTmp);
+    psPlane tpCoord;
+    psS32 testStatus = 0;
+
+    //
+    // I'm not convinced that the p_psProject() and p_psDeproject() functions work
+    // correctly.  If we project a set of coordinates over a wide range of (R, D)
+    // values, then deproject them, the original (R, D) values are only produced
+    // when D is larger than 0.  This code demonstrates that.  I also created tests
+    // that currently fail in tst_psCoord01.c.  I have a workaround in the function
+    // XXXDeproject() in pmAstrometry.c.
+    //
+    if (0) {
+        // This loop goes from (R, D) -> (X, Y) -> (R, D)
+        psPlane planeCoord01;
+        psSphere skyCoord01;
+        psSphere skyCoord02;
+        #define DEG_INC 15.0
+
+        for (psF32 R = -90.0 ; R <= 90.0 ; R+= DEG_INC) {
+            for (psF32 D = -90.0 ; D <= 90.0 ; D+= DEG_INC) {
+                if ((fabs(R) != 90.0) && (fabs(D) != 90.0)) {
+                    skyCoord01.r = DEG_TO_RAD(R);
+                    skyCoord01.d = DEG_TO_RAD(D);
+                    p_psProject(&planeCoord01, &skyCoord01, myFPA->projection);
+                    p_psDeproject(&skyCoord02, &planeCoord01, myFPA->projection);
+                    printf("(%.2fr %.2fd) (%.2fr %.2fd) -> (%.2f %.2f) -> (%.2fr %.2fd)", R, D,
+                           skyCoord01.r, skyCoord01.d,
+                           planeCoord01.x, planeCoord01.y,
+                           skyCoord02.r, skyCoord02.d);
+                    if ((fabs(skyCoord01.r - skyCoord02.r) < FLT_EPSILON) &&
+                            (fabs(skyCoord01.d - skyCoord02.d) < FLT_EPSILON)) {
+                        printf(": CORRECT\n");
+                    } else {
+                        printf(": WRONG\n");
+                    }
+                }
+            }
+        }
+        psFree(myFPA);
+        return(0);
+    }
+    if (0) {
+        // This loop goes from (X, Y) -> (R, D) -> (X, Y)
+        #define SPACE_INC 4.0
+        for(testCoord.x=-CELL_HEIGHT;testCoord.x<=CELL_HEIGHT;testCoord.x+=SPACE_INC)
+        {
+            for (testCoord.y=-CELL_WIDTH;testCoord.y<=CELL_WIDTH;testCoord.y+=SPACE_INC) {
+                psPlane planeCoord01;
+                psPlane planeCoord02;
+                psSphere skyCoord01;
+                psSphere skyCoord02;
+                p_psDeproject(&skyCoord01, &testCoord, myFPA->projection);
+                p_psProject(&planeCoord01, &skyCoord01, myFPA->projection);
+                p_psDeproject(&skyCoord02, &planeCoord01, myFPA->projection);
+                p_psProject(&planeCoord02, &skyCoord02, myFPA->projection);
+                printf("Plane: (%.2f %.2f) -> (%.2fr %.2fd) -> (%.2f %.2f)\n",
+                       testCoord.x, testCoord.y,
+                       skyCoord01.r, skyCoord01.d,
+                       planeCoord01.x, planeCoord01.y);
+                /*
+                                printf("Plane: (%.2f %.2f) -> (%.2f %.2f) -> (%.2f %.2f)\n",
+                                        testCoord.x, testCoord.y, planeCoord01.x, planeCoord01.y,
+                                        planeCoord02.x, planeCoord02.y);
+                                printf("Sphere: (%.2f %.2f) -> (%.2f %.2f)\n",
+                                        skyCoord01.r, skyCoord01.d, skyCoord02.r, skyCoord02.d);
+                                printf("Plane: (%.2f %.2f) -> (%.2fd %.2fr) -> (%.2f %.2f) -> (%.2fd %.2fr) -> (%.2f %.2f)\n",
+                                        testCoord.x, testCoord.y,
+                                        skyCoord01.r, skyCoord01.d,
+                                        planeCoord01.x, planeCoord01.y,
+                                        skyCoord02.r, skyCoord02.d,
+                                        planeCoord02.x, planeCoord02.y);
+                */
+            }
+        }
+        psFree(myFPA);
+        return(0);
+    }
+    //
+    // We iterate through all cells on all chips on the fpa.  We determine
+    // the expected fpaCcoord.
+    //
+
+    for (psS32 chip=0;chip<NUM_CHIPS;chip++) {
+        for (psS32 cell=0;cell<NUM_CELLS;cell++) {
+            for(x=0;x<CELL_HEIGHT;x++) {
+                for (y=0;y<CELL_WIDTH;y++) {
+                    fpaCoord.x = (psF64) x;
+                    fpaCoord.y = (psF64) (y + (chip * (CHIP_WIDTH + CHIP_GAP)) +
+                                          (cell * (CELL_WIDTH + CELL_GAP)));
+                    if (VERBOSE) {
+                        printf("------------------ (%.2f, %.2f) ------------------\n", fpaCoord.x, fpaCoord.y);
+                        printf("(chip, cell, x, y) is (%d, %d, %d, %d)\n", chip, cell, x, y);
+                    }
+                    pmChip* tmpChip = pmChipInFPA(&fpaCoord, myFPA);
+                    myCell = pmCellInFPA(&fpaCoord, myFPA);
+
+                    if ((myCell == NULL) || (tmpChip == NULL)) {
+                        if (tmpChip == NULL) {
+                            printf("TEST ERROR: pmChipInFPA() returned NULL\n");
+                            testStatus = 1;
+                        } else if (myCell == NULL) {
+                            printf("TEST ERROR: pmCellInFPA(): returned NULL\n");
+                            testStatus = 1;
+                        }
+                    } else {
+                        pmCoordFPAToChip(&chipCoord, &fpaCoord, tmpChip);
+                        pmCoordChipToCell(&cellCoord, &chipCoord, myCell);
+
+                        if (x != (psS32) cellCoord.x) {
+                            printf("TEST ERROR: pmCoordFPAToChip()->pmCoordChipToCell(): x coord was %d (%f), should be %d\n", (psS32) cellCoord.x, cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (y != (psS32) cellCoord.y) {
+                            printf("TEST ERROR: pmCoordFPAToChip()->pmCoordChipToCell(): y coord was %d (%f), should be %d\n", (psS32) cellCoord.y, cellCoord.y, y);
+                            testStatus = 1;
+                        }
+
+                        pmCoordCellToChip(&testCoord, &cellCoord, myCell);
+                        if (fabs(testCoord.x - chipCoord.x) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordCellToChip() x coord was %.2f, should be %d\n", cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (fabs(testCoord.y - chipCoord.y) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordCellToChip() y coord was %.2f, should be %d\n", cellCoord.y, y);
+                            testStatus = 1;
+                        }
+
+                        pmCell *myCell2 = pmCellInChip(&chipCoord, tmpChip);
+                        if (myCell2 != myCell) {
+                            printf("TEST ERROR: pmCellInChip() != pmCellInChip(pmChipInFPA()) (%p %p)\n", myCell2, myCell);
+                            testStatus = 1;
+                        }
+
+                        pmCoordChipToFPA(&testCoord, &chipCoord, tmpChip);
+                        if (fabs(testCoord.x - x) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordChipToFPA() x coord was %.2f, should be %d\n", cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (fabs(testCoord.y - fpaCoord.y) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordChipToFPA() y coord was %.2f, should be %.2f\n", cellCoord.y, fpaCoord.y);
+                            testStatus = 1;
+                        }
+
+                        pmCoordFPAToTP(&testCoord, &fpaCoord, 0.0, 0.0, myFPA);
+                        if (fabs(testCoord.x - fpaCoord.x) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordFPAToTP() x coord was %.2f, should be %d\n", cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (fabs(testCoord.y - fpaCoord.y) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordFPAToTP() y coord was %.2f, should be %d\n", cellCoord.y, y);
+                            testStatus = 1;
+                        }
+
+                        //
+                        // Test pmCoordTPToSky() -> pmCoordSkyToTP()
+                        //
+                        if (1) {
+                            psSphere *rc = pmCoordTPToSky(skyCoord, &testCoord, myFPA->projection);
+                            if (rc == NULL) {
+                                printf("pmCoordTPToSky() failed.\n");
+                            } else {
+                                psPlane *rc = pmCoordSkyToTP(&tpCoord, skyCoord, myFPA->projection);
+                                if (rc == NULL) {
+                                    printf("pmCoordSkyToTP() failed.\n");
+                                } else {
+                                    if (fabs(testCoord.x - tpCoord.x) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordTPToSky()/pmCoordSkyToTP() x coord was %.2f, should be %.2f\n", tpCoord.x, testCoord.x);
+                                        testStatus = 1;
+                                    }
+                                    if (fabs(testCoord.y - tpCoord.y) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordTPToSky()/pmCoordSkyToTP() y coord was %.2f, should be %.2f\n", tpCoord.y, testCoord.y);
+                                        testStatus = 1;
+                                    }
+                                    if (VERBOSE) {
+                                        printf("(%.2f %.2f) -> (%.2f %.2f) -> (%.2f %.2f)\n", testCoord.x, testCoord.y, skyCoord->r, skyCoord->d, tpCoord.x, tpCoord.y);
+                                    }
+                                }
+                            }
+                        }
+
+                        //
+                        // Test pmCoordCellToSky() -> pmCoordSkyToCell()
+                        //
+                        if (1) {
+                            psPlane tmpCellCoord;
+                            psSphere *rc = pmCoordCellToSky(skyCoord, &cellCoord, 0.0, 0.0, myCell);
+                            if (rc == NULL) {
+                                printf("pmCoordCellToSky() failed.\n");
+                            } else {
+                                psPlane *rc = pmCoordSkyToCell(&tmpCellCoord, skyCoord, 0.0, 0.0, myCell);
+                                if (rc == NULL) {
+                                    printf("pmCoordSkyToCell() failed.\n");
+                                } else {
+                                    if (fabs(cellCoord.x - tmpCellCoord.x) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordCellToSky()/pmCoordSkyToCell() x coord was %.2f, should be %.2f\n", tmpCellCoord.x, cellCoord.x);
+                                        testStatus = 1;
+                                    }
+                                    if (fabs(cellCoord.y - tmpCellCoord.y) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordCellToSky()/pmCoordSkyToCell() y coord was %.2f, should be %.2f\n", tmpCellCoord.y, cellCoord.y);
+                                        testStatus = 1;
+                                    }
+                                    if (VERBOSE) {
+                                        printf("(%.2f %.2f) -> (%.2f %.2f) -> (%.2f %.2f)\n", testCoord.x, testCoord.y, skyCoord->r, skyCoord->d, tmpCellCoord.x, tmpCellCoord.y);
+                                    }
+                                }
+                            }
+                        }
+
+                        //
+                        // Test pmCoordCellToSkyQuick() -> pmCoordSkyToCellQuick()
+                        // I'm not sure how to test this in a system with chip and cell gaps.
+                        // There's no way to create an accurate polynomial transform from
+                        // a cell to the sky where there are cell, or chip gaps.
+                        //
+                        if ((NUM_CHIPS == 1) && (NUM_CELLS == 1)) {
+                            psPlane tmpCellCoord;
+                            psSphere *rc = pmCoordCellToSkyQuick(skyCoord, &cellCoord, myCell);
+                            if (rc == NULL) {
+                                printf("pmCoordCellToSkyQuick() failed.\n");
+                            } else {
+                                psPlane *rc = pmCoordSkyToCellQuick(&tmpCellCoord, skyCoord, myCell);
+                                if (rc == NULL) {
+                                    printf("pmCoordSkyToCellQuick() failed.\n");
+                                } else {
+                                    if (fabs(cellCoord.x - tmpCellCoord.x) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordCellToSky()/pmCoordSkyToCell() x coord was %.2f, should be %.2f\n", tmpCellCoord.x, cellCoord.x);
+                                        testStatus = 1;
+                                    }
+                                    if (fabs(cellCoord.y - tmpCellCoord.y) > FLT_EPSILON) {
+                                        printf("TEST ERROR: pmCoordCellToSky()/pmCoordSkyToCell() y coord was %.2f, should be %.2f\n", tmpCellCoord.y, cellCoord.y);
+                                        testStatus = 1;
+                                    }
+                                    if (VERBOSE) {
+                                        printf("(%.2f %.2f) -> (%.2f %.2f) -> (%.2f %.2f)\n", testCoord.x, testCoord.y, skyCoord->r, skyCoord->d, tmpCellCoord.x, tmpCellCoord.y);
+                                    }
+                                }
+                            }
+                        }
+
+                        pmCoordCellToFPA(&testCoord, &cellCoord, myCell);
+                        if (fabs(testCoord.x - fpaCoord.x) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordCellToFPA() x coord was %.2f, should be %d\n", cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (fabs(testCoord.y - fpaCoord.y) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordCellToFPA() y coord was %.2f, should be %d\n", cellCoord.y, y);
+                            testStatus = 1;
+                        }
+
+                        pmCoordTPToFPA(&testCoord, &fpaCoord, 0.0, 0.0, myFPA);
+                        if (fabs(testCoord.x - fpaCoord.x) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordTPToFPA() x coord was %.2f, should be %d\n", cellCoord.x, x);
+                            testStatus = 1;
+                        }
+                        if (fabs(testCoord.y - fpaCoord.y) > FLT_EPSILON) {
+                            printf("TEST ERROR: pmCoordTPToFPA() y coord was %.2f, should be %d\n", cellCoord.y, y);
+                            testStatus = 1;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    psFree(myFPA);
+    psFree(skyCoord);
+
+    return(testStatus);
+}
+
+/******************************************************************************
+test4(): This routine wil test the pmFPACheckParents() function.  We generate
+an pmFPA hierarchy, then set the parents of each readout/cell/chip to NULL,
+then call pmFPACheckParents() to restore them, then we ensure they were
+restored.
+ *****************************************************************************/
+psS32 test4( void )
+{
+    psS32 testStatus = 0;
+
+    //
+    // Generate a pmFPA hierarchy.
+    //
+    pmFPA *tmpFPA = genSystem();
+
+    //
+    // We set the parents of each readout/cell/chip to NULL.
+    //
+    for (psS32 chipID = 0; chipID < tmpFPA->chips->n ; chipID++) {
+        pmChip *tmpChip = (pmChip *) tmpFPA->chips->data[chipID];
+        tmpChip->parent = NULL;
+
+        for (psS32 cellID = 0; cellID < tmpChip->cells->n ; cellID++) {
+            pmCell *tmpCell = (pmCell *) tmpChip->cells->data[cellID];
+            tmpCell->parent = NULL;
+
+            for (psS32 readoutID = 0; readoutID < tmpCell->readouts->n ; readoutID++) {
+                pmReadout *tmpReadout = (pmReadout *) tmpCell->readouts->data[readoutID];
+                tmpReadout->parent = NULL;
+            }
+        }
+    }
+
+    //
+    // Ensure that pmFPACheckParents() returned FALSE.
+    //
+    psBool rc = pmFPACheckParents(tmpFPA);
+    if (rc != false) {
+        printf("TEST ERROR: pmCheckParents() returned TRUE.\n");
+        testStatus = 1;
+    }
+
+    //
+    // Ensure that the parent members are right.
+    //
+    for (psS32 chipID = 0; chipID < tmpFPA->chips->n ; chipID++) {
+        pmChip *tmpChip = (pmChip *) tmpFPA->chips->data[chipID];
+        if (tmpChip->parent != tmpFPA) {
+            printf("TEST ERROR: pmCheckParents() did not restore Chip->parent.\n");
+            testStatus = 2;
+        }
+
+        for (psS32 cellID = 0; cellID < tmpChip->cells->n ; cellID++) {
+            pmCell *tmpCell = (pmCell *) tmpChip->cells->data[cellID];
+            if (tmpCell->parent != tmpChip) {
+                printf("TEST ERROR: pmCheckParents() did not restore Cell->parent.\n");
+                testStatus = 3;
+            }
+
+            for (psS32 readoutID = 0; readoutID < tmpCell->readouts->n ; readoutID++) {
+                pmReadout *tmpReadout = (pmReadout *) tmpCell->readouts->data[readoutID];
+                if (tmpReadout->parent != tmpCell) {
+                    printf("TEST ERROR: pmCheckParents() did not restore Readout->parent.\n");
+                    testStatus = 4;
+                }
+            }
+        }
+    }
+
+    psFree(tmpFPA);
+    return(testStatus);
+}
+
+/******************************************************************************
+test5(): This routine wil test the pmFPASelectChip() and pmFPAExcludeChip()
+functions.  We generate an pmFPA hierarchy, then set the ->valid members with
+those routines, then verify.
+ *****************************************************************************/
+psS32 test5( void )
+{
+    psS32 testStatus = 0;
+    pmChip *tmpChip = NULL;
+
+    //
+    // Generate a pmFPA hierarchy.
+    //
+    pmFPA *tmpFPA = genSystem();
+
+    //
+    // We test the ->valid member for each chip.
+    //
+    for (psS32 i = 0 ; i < tmpFPA->chips->n ; i++) {
+        tmpChip = (pmChip *) tmpFPA->chips->data[i];
+        if ((tmpChip == NULL) || (tmpChip->valid != false)) {
+            printf("TEST ERROR: Could not properly generate an FPA hierarchy.\n");
+            testStatus = 1;
+        }
+    }
+
+    //
+    // Exclude chip number 0, include all others, then test return value
+    //
+    psS32 numChips = pmFPAExcludeChip(tmpFPA, 0);
+    if (numChips != (NUM_CHIPS-1)) {
+        printf("TEST ERROR: pmFPAExcludeChip() did not return the correct number of chips.\n");
+        testStatus = 2;
+    }
+
+    //
+    // We test the ->valid member for each chip.
+    //
+    tmpChip = (pmChip *) tmpFPA->chips->data[0];
+    if (tmpChip->valid != false) {
+        printf("TEST ERROR: pmFPAExcludeChip() did not set the proper chip->valid to FALSE.\n");
+        testStatus = 3;
+    }
+    for (psS32 i = 1 ; i < tmpFPA->chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) tmpFPA->chips->data[i];
+        if (tmpChip->valid != true) {
+            printf("TEST ERROR: pmFPAExcludeChip() did not set the proper chip->valids to FALSE.\n");
+            testStatus = 4;
+        }
+    }
+
+
+    //
+    // Include chip number 0, exclude all others, then test return value
+    //
+    psBool tmpBool = pmFPASelectChip(tmpFPA, 0);
+    if (tmpBool != true) {
+        printf("TEST ERROR: pmFPASelectChip() returned FALSE.\n");
+        testStatus = 5;
+    }
+
+    //
+    // We test the ->valid member for each chip.
+    //
+    tmpChip = (pmChip *) tmpFPA->chips->data[0];
+    if (tmpChip->valid != true) {
+        printf("TEST ERROR: pmFPASelectChip() did not set the proper chip->valid to FALSE.\n");
+        testStatus = 6;
+    }
+    for (psS32 i = 1 ; i < tmpFPA->chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) tmpFPA->chips->data[i];
+        if (tmpChip->valid != false) {
+            printf("TEST ERROR: pmFPASelectChip() did not set the proper chip->valids to FALSE.\n");
+            testStatus = 7;
+        }
+    }
+
+    psFree(tmpFPA);
+    return(testStatus);
+}
Index: /tags/RHL_v1_1/psModules/test/astrom/verified/tst_pmAstrometry.stderr
===================================================================
--- /tags/RHL_v1_1/psModules/test/astrom/verified/tst_pmAstrometry.stderr	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/astrom/verified/tst_pmAstrometry.stderr	(revision 9533)
@@ -0,0 +1,36 @@
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmAstrometry.c                                         *
+*            TestPoint: pmAstrometry{pmFPAAlloc}                                   *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmAstrometry{pmFPAAlloc} | tst_pmAstrometry.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmAstrometry.c                                         *
+*            TestPoint: pmAstrometry{pmChipAlloc}                                  *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmAstrometry{pmChipAlloc} | tst_pmAstrometry.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmAstrometry.c                                         *
+*            TestPoint: pmAstrometry{pmCellAlloc}                                  *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmAstrometry{pmCellAlloc} | tst_pmAstrometry.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmAstrometry.c                                         *
+*            TestPoint: pmAstrometry{pmReadoutAlloc}                               *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmAstrometry{pmReadoutAlloc} | tst_pmAstrometry.c)
+
Index: /tags/RHL_v1_1/psModules/test/camera/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/camera/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/camera/.cvsignore	(revision 9533)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmAstrometry
Index: /tags/RHL_v1_1/psModules/test/camera/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/camera/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/camera/Makefile.am	(revision 9533)
@@ -0,0 +1,18 @@
+AM_LDFLAGS = -L$(top_builddir)/src -lpsmodules $(PSMODULES_LIBS)
+AM_CFLAGS  = @AM_CFLAGS@ $(PSMODULES_CFLAGS) $(SRCINC)
+AM_CPPFLAGS = $(SRCINC) $(PSLIB_CFLAGS)
+
+TESTS =\
+    tst_pmAstrometry
+
+tst_pmAstrometry_SOURCES = tst_pmAstrometry.c
+
+check_PROGRAMS = $(TESTS)
+
+TESTS_ENVIRONMENT = perl $(top_srcdir)/test/runTest --verified=$(srcdir)/verified
+
+tests: $(TESTS)
+
+CLEANFILES = $(TESTS) temp/* *~
+
+test: check
Index: /tags/RHL_v1_1/psModules/test/camera/tst_pmAstrometry.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/camera/tst_pmAstrometry.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/camera/tst_pmAstrometry.c	(revision 9533)
@@ -0,0 +1,615 @@
+/** @file  tst_pmAstrometry.c
+ *
+ *  @brief Contains the tests: pmAstrometry.[ch].  The pmxxxAlloc()
+ *  and psFree() functionality are used here.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  XXX: Untested: pmFPACheckParents()
+ *  XXX: Create the pmHDU alloc/free function, test them here
+ *
+ *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-05-01 01:58:04 $
+ *
+ *  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#include "config.h"
+#include <math.h>
+#include <string.h>
+#include "psTest.h"
+#include "pslib_strict.h"
+#include "pmAstrometry.h"
+
+static psS32 testFPAAlloc(void);
+static psS32 testChipAlloc(void);
+static psS32 testCellAlloc(void);
+static psS32 testReadoutAlloc(void);
+
+testDescription tests[] = {
+                              {testFPAAlloc,739,"pmFPAAlloc",0,false},
+                              {testChipAlloc,740,"pmChipAlloc",0,false},
+                              {testCellAlloc,741,"pmCellAlloc",0,false},
+                              {testReadoutAlloc,742,"pmReadoutAlloc",0,false},
+                              {NULL}
+                          };
+
+#define CHIP_ALLOC_NAME "ChipName"
+#define CELL_ALLOC_NAME "CellName"
+#define MISC_NUM 32
+#define MISC_NAME "META00"
+#define MISC_NAME2 "META01"
+#define NUM_BIAS_DATA 10
+#define TEST_NUM_ROWS 32
+#define TEST_NUM_COLS 32
+
+psPlaneTransform *PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM()
+{
+    psPlaneTransform *pt = psPlaneTransformAlloc(1, 1);
+    pt->x->coeff[1][0] = 1.0;
+    pt->y->coeff[0][1] = 1.0;
+    return(pt);
+}
+
+psPlaneDistort *PS_CREATE_4D_IDENTITY_PLANE_DISTORT()
+{
+    psPlaneDistort *pd = psPlaneDistortAlloc(1, 1, 1, 1);
+    pd->x->coeff[1][0][0][0] = 1.0;
+    pd->y->coeff[0][1][0][0] = 1.0;
+    return(pd);
+}
+
+/******************************************************************************
+generateSimpleFPA(): This function generates a pmFPA data structure and then
+populates its members with real data.  We do this to ensure that the data is
+later being psFree()'ed correctly.
+ *****************************************************************************/
+pmFPA *generateSimpleFPA()
+{
+    psBool rc;
+    pmFPA* fpa = pmFPAAlloc(psMetadataAlloc());
+
+    if (fpa == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc() returned a NULL.");
+        return(NULL);
+    }
+
+    //
+    // Test and create camera metadata.
+    //
+    if (fpa->camera == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: fpa->camera is NULL.");
+        psFree(fpa);
+        return(NULL);
+    } else {
+        rc = psMetadataAddS32((psMetadata *) fpa->camera, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+        if (rc == false) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not add metadata to fpa->camera.");
+            psFree(fpa);
+            return(NULL);
+        }
+        psS32 tmpS32 = psMetadataLookupS32(&rc, fpa->camera, MISC_NAME);
+        if ((rc == false) || (tmpS32 != MISC_NUM)) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not read metadata from fpa->camera.");
+            psFree(fpa);
+            return(NULL);
+        }
+    }
+
+    //
+    // Create various transforms and projections.
+    //
+    fpa->fromTangentPlane = PS_CREATE_4D_IDENTITY_PLANE_DISTORT();
+    fpa->toTangentPlane = PS_CREATE_4D_IDENTITY_PLANE_DISTORT();
+    fpa->projection = psProjectionAlloc(0.0,0.0,10.0,10.0,PS_PROJ_TAN);
+
+    //
+    // Ensure fpa concepts metadata was allocated properly.
+    //
+    if (fpa->concepts == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set fpa->concepts.");
+        psFree(fpa);
+        return(NULL);
+    } else {
+        rc = psMetadataAddS32(fpa->concepts, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+        if (rc == false) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not add data to fpa->concepts.");
+            psFree(fpa);
+            return(NULL);
+        }
+        psS32 tmpS32 = psMetadataLookupS32(&rc, fpa->concepts, MISC_NAME);
+        if ((rc == false) || (tmpS32 != MISC_NUM)) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not read metadata from fpa->concepts.");
+            psFree(fpa);
+            return(NULL);
+        }
+    }
+
+    //
+    // Create ->analysis metadata.
+    //
+    fpa->analysis = psMetadataAlloc();
+    rc = psMetadataAddS32(fpa->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    //
+    // We test the fpa->chips array later.
+    //
+
+    //
+    // How to test the p_pmHDU *hdu member?
+    //
+
+    //
+    // Create ->phu metadata.
+    //
+    fpa->phu = psMetadataAlloc();
+    rc = psMetadataAddS32(fpa->phu, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    return(fpa);
+}
+
+
+psS32 main(psS32 argc, char* argv[])
+{
+    psLogSetLevel(PS_LOG_INFO);
+    psLogSetFormat("HLNM");
+
+    return ! runTestSuite(stderr,"pmAstrometry",tests,argc,argv);
+}
+
+/******************************************************************************
+testFPAAlloc()
+    1: We ensure that pmFPAAlloc() properly allocates a pmFPA struct.
+    2: We populate the members with real data to ensure they are being
+       free'ed correctly.
+ *****************************************************************************/
+static psS32 testFPAAlloc(void)
+{
+    psMetadata *camera = psMetadataAlloc();
+    pmFPA* fpa = pmFPAAlloc(camera);
+
+    if (fpa == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc() returned a NULL.");
+        return 1;
+    }
+
+    if (fpa->fromTangentPlane != NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->fromTangentPlane to NULL.");
+        return 2;
+    }
+
+    if (fpa->toTangentPlane != NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->toTangentPlane to NULL.");
+        return 3;
+    }
+    if (fpa->projection != NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->projection to NULL.");
+        return 4;
+    }
+
+    if (fpa->concepts == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->concepts.");
+        return 5;
+    }
+
+    if (fpa->analysis != NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->analysis to NULL.");
+        return 6;
+    }
+
+    if (fpa->camera != camera) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->camera.");
+        return 7;
+    }
+
+    if (fpa->chips == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->chips.");
+        return 8;
+    }
+
+    if (fpa->hdu != NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc did not set ->hdu to NULL.");
+        return 9;
+    }
+    psFree(fpa);
+
+    //
+    // Populate the pmFPA struct with real data to ensure they were
+    // psFree()'ed correctly.
+    //
+    fpa = generateSimpleFPA();
+    if (fpa == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: generateSimpleFPA() returned NULL.");
+        return(15);
+    }
+    psFree(fpa);
+
+    return(0);
+}
+
+/******************************************************************************
+generateSimpleChip(): This function generates a pmChip data structure and then
+populates its members with real data.  We do this to ensure that the data is
+later being psFree()'ed correctly.
+ *****************************************************************************/
+pmChip *generateSimpleChip(pmFPA *fpa)
+{
+    psBool rc;
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    if (chip == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmChipAlloc returned a NULL.");
+        return(NULL);
+    }
+    chip->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    chip->fromFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    //
+    // We already ensured that chip->concepts was working properly.
+    //
+
+    //
+    // Create ->analysis metadata.
+    //
+    chip->analysis = psMetadataAlloc();
+    rc = psMetadataAddS32(chip->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    //
+    // We test the chip->cells array later.
+    //
+
+    //
+    // How to test the p_pmHDU *hdu member?
+    //
+
+    return(chip);
+}
+
+static psS32 testChipAlloc(void)
+{
+    pmFPA* fpa = generateSimpleFPA();
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    if (chip == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmChipAlloc returned a NULL.");
+        return 1;
+    }
+
+    if (chip->col0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->col0 set improperly.\n");
+        return 5;
+    }
+
+    if (chip->row0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->row0 set improperly.\n");
+        return 6;
+    }
+
+    if (chip->toFPA != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->toChip set improperly.\n");
+        return 7;
+    }
+
+    if (chip->fromFPA != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->toFPA set improperly.\n");
+        return 8;
+    }
+
+    if (chip->concepts == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->concepts set improperly.\n");
+        return 21;
+    } else {
+        psMetadataItem *tmpMeta = psMetadataLookup(chip->concepts, "CHIP.NAME");
+        if (0 != strcmp((char *) tmpMeta->data.V, CHIP_ALLOC_NAME)) {
+            psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: The metadata was set improperly.\n");
+            return (32);
+        }
+        // XXX: Code a test to ensure the metadata has the correct type
+    }
+
+    if (chip->analysis != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->analysis set improperly.\n");
+        return 10;
+    }
+
+    if (chip->cells == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->cells set improperly.\n");
+        return 22;
+    }
+
+    if (chip->parent != fpa) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->parent set improperly.\n");
+        return 23;
+    }
+
+    if (chip->valid != false) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->valid set improperly.\n");
+        return 24;
+    }
+
+    if (chip->hdu != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: chip->hdu set improperly.\n");
+        return 25;
+    }
+    psFree(fpa);
+
+    //
+    // Populate the pmChip struct with real data to ensure they were
+    // psFree()'ed correctly.
+    //
+    fpa = generateSimpleFPA();
+    chip = generateSimpleChip(fpa);
+    psFree(fpa);
+
+    return(0);
+}
+
+/******************************************************************************
+generateSimpleCell(): This function generates a pmCell data structure and then
+populates its members with real data.  We do this to ensure that the data is
+later being psFree()'ed correctly.
+ *****************************************************************************/
+pmCell *generateSimpleCell(pmFPA *fpa, pmChip *chip)
+{
+    psBool rc;
+    pmCell *cell = pmCellAlloc(chip, (psMetadata *) fpa->camera, CELL_ALLOC_NAME);
+    if (cell == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmCellAlloc returned a NULL.");
+        return(NULL);
+    }
+    cell->toChip = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    cell->toFPA = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    cell->toSky = PS_CREATE_2D_IDENTITY_PLANE_TRANSFORM();
+    //
+    // We already ensured that cell->concepts was working properly.
+    //
+
+    //
+    // Test camera metadata.
+    //
+    if (cell->camera == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->camera is NULL.");
+        psFree(fpa);
+        return(NULL);
+    } else {
+        rc = psMetadataAddS32((psMetadata *) cell->camera, PS_LIST_HEAD, MISC_NAME2, 0, NULL, MISC_NUM);
+        if (rc == false) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not add metadata to cell->camera.");
+            psFree(fpa);
+            return(NULL);
+        }
+        psS32 tmpS32 = psMetadataLookupS32(&rc, cell->camera, MISC_NAME2);
+        if ((rc == false) || (tmpS32 != MISC_NUM)) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not read metadata from cell->camera.");
+            psFree(fpa);
+            return(NULL);
+        }
+    }
+
+    //
+    // Create ->analysis metadata.
+    //
+    cell->analysis = psMetadataAlloc();
+    rc = psMetadataAddS32(cell->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+
+    //
+    // We test the cell->readouts array later.
+    //
+
+    //
+    // How to test the p_pmHDU *hdu member?
+    //
+
+    return(cell);
+}
+
+static psS32 testCellAlloc(void)
+{
+    pmFPA* fpa = generateSimpleFPA();
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    pmCell *cell = pmCellAlloc(chip, (psMetadata *) fpa->camera, CELL_ALLOC_NAME);
+    if (cell == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmCellAlloc returned a NULL.n");
+        return 3;
+    }
+
+    if (cell->col0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->col0 set improperly.\n");
+        return 5;
+    }
+
+    if (cell->row0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->row0 set improperly.\n");
+        return 6;
+    }
+
+    if (cell->toChip != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->toChip set improperly.\n");
+        return 7;
+    }
+
+    if (cell->toFPA != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->toFPA set improperly.\n");
+        return 8;
+    }
+
+    if (cell->toSky != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->toSky set improperly.\n");
+        return 9;
+    }
+
+    if (cell->concepts == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->concepts set improperly.\n");
+        return 21;
+    } else {
+        psMetadataItem *tmpMeta = psMetadataLookup(cell->concepts, "CELL.NAME");
+        if (0 != strcmp((char *) tmpMeta->data.V, CELL_ALLOC_NAME)) {
+            psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: The metadata was set improperly.\n");
+            return (32);
+        }
+        // XXX: Code a test to ensure the metadata has the correct type
+    }
+
+    if (cell->camera != fpa->camera) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->camera set improperly.\n");
+        return 20;
+    }
+
+    if (cell->analysis != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->analysis set improperly.\n");
+        return 10;
+    }
+
+    if (cell->readouts == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->readouts set improperly.\n");
+        return 22;
+    }
+
+    if (cell->parent != chip) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->parent set improperly.\n");
+        return 23;
+    }
+
+    if (cell->valid != false) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->valid set improperly.\n");
+        return 24;
+    }
+
+    if (cell->hdu != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: cell->hdu set improperly.\n");
+        return 27;
+    }
+    psFree(fpa);
+
+    //
+    // Populate the pmCell struct with real data to ensure they were
+    // psFree()'ed correctly.
+    //
+    fpa = generateSimpleFPA();
+    chip = generateSimpleChip(fpa);
+    cell = generateSimpleCell(fpa, chip);
+    psFree(fpa);
+
+    return(0);
+}
+
+/******************************************************************************
+generateSimpleReadout(): This function generates a pmReadout data structure and then
+populates its members with real data.  We do this to ensure that the data is
+later being psFree()'ed correctly.
+ *****************************************************************************/
+pmReadout *generateSimpleReadout(pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    psBool rc;
+    pmReadout *readout = pmReadoutAlloc(cell);
+    if (readout == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmReadoutAlloc returned a NULL.");
+        return(NULL);
+    }
+    readout->image = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+    readout->mask = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_U8);
+    readout->weight = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+
+    //
+    // Create a psList of bias data.
+    //
+    for (psS32 i = 0 ; i < NUM_BIAS_DATA ; i++) {
+        psImage *tmpImage = psImageAlloc(TEST_NUM_COLS, TEST_NUM_ROWS, PS_TYPE_F32);
+        if (readout->bias == NULL) {
+            readout->bias = psListAlloc(tmpImage);
+        } else {
+            psListAdd(readout->bias, PS_LIST_HEAD, tmpImage);
+        }
+    }
+
+    //
+    // Test readout->analysis metadata.
+    //
+    if (readout->analysis == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: readout->analysis is NULL.");
+        psFree(fpa);
+        return(NULL);
+    } else {
+        rc = psMetadataAddS32((psMetadata *) readout->analysis, PS_LIST_HEAD, MISC_NAME, 0, NULL, MISC_NUM);
+        if (rc == false) {
+            psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: could not add metadata to readout->analysis.");
+            psFree(fpa);
+            return(NULL);
+        }
+    }
+
+    return(readout);
+}
+
+
+static psS32 testReadoutAlloc(void)
+{
+    pmFPA* fpa = generateSimpleFPA();
+    pmChip *chip = pmChipAlloc(fpa, CHIP_ALLOC_NAME);
+    pmCell *cell = pmCellAlloc(chip, (psMetadata *) fpa->camera, CELL_ALLOC_NAME);
+    pmReadout *readout = pmReadoutAlloc(cell);
+    if (readout == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmReadoutAlloc returned a NULL.\n");
+        return 4;
+    }
+
+    if (readout->col0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->col0 set improperly.\n");
+        return 5;
+    }
+
+    if (readout->row0 != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->row0 set improperly.\n");
+        return 6;
+    }
+
+    if (readout->colBins != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->colBins set improperly.\n");
+        return 7;
+    }
+
+    if (readout->rowBins != -1) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->colBins set improperly.\n");
+        return 8;
+    }
+
+    if (readout->image != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->image set improperly.\n");
+        return 10;
+    }
+
+    if (readout->mask != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->mask set improperly.\n");
+        return 12;
+    }
+
+    if (readout->weight != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->weight set improperly.\n");
+        return 14;
+    }
+
+    if (readout->bias != NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->bias set improperly.\n");
+        return 16;
+    }
+
+    if (readout->analysis == NULL) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->analysis set improperly.\n");
+        return 18;
+    }
+
+    if (readout->parent != cell) {
+        psLogMsg(__func__, PS_LOG_ERROR, "TEST ERROR: pmReadout->parent set improperly.\n");
+        return 20;
+    }
+    psFree(fpa);
+
+    //
+    // Populate the pmReadout struct with real data to ensure they were
+    // psFree()'ed correctly.
+    //
+    fpa = generateSimpleFPA();
+    chip = generateSimpleChip(fpa);
+    cell = generateSimpleCell(fpa, chip);
+    readout = generateSimpleReadout(fpa, chip, cell);
+    psFree(fpa);
+
+    return(0);
+}
Index: /tags/RHL_v1_1/psModules/test/concepts/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/concepts/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/concepts/.cvsignore	(revision 9533)
@@ -0,0 +1,4 @@
+.deps
+.libs
+Makefile
+Makefile.in
Index: /tags/RHL_v1_1/psModules/test/concepts/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/concepts/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/concepts/Makefile.am	(revision 9533)
@@ -0,0 +1,15 @@
+AM_LDFLAGS = -L$(top_builddir)/src -lpsmodules $(PSMODULES_LIBS)
+AM_CFLAGS  = @AM_CFLAGS@ $(PSMODULES_CFLAGS) $(SRCINC)
+AM_CPPFLAGS = $(SRCINC) $(PSLIB_CFLAGS)
+
+TESTS =
+
+check_PROGRAMS = $(TESTS)
+
+TESTS_ENVIRONMENT = perl $(top_srcdir)/test/runTest --verified=$(srcdir)/verified
+
+tests: $(TESTS)
+
+CLEANFILES = $(TESTS) temp/* *~
+
+test: check
Index: /tags/RHL_v1_1/psModules/test/config/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/config/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/config/.cvsignore	(revision 9533)
@@ -0,0 +1,7 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmConfig
+SampleIPPConfig
Index: /tags/RHL_v1_1/psModules/test/config/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/config/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/config/Makefile.am	(revision 9533)
@@ -0,0 +1,25 @@
+AM_LDFLAGS = -L$(top_builddir)/src -lpsmodules $(PSMODULES_LIBS)
+AM_CFLAGS  = @AM_CFLAGS@ $(PSMODULES_CFLAGS) $(SRCINC)
+
+TESTS = \
+    tst_pmConfig
+
+tst_pmConfig_SOURCES = tst_pmConfig.c
+
+check_PROGRAMS = $(TESTS)
+
+check_DATA = \
+    SampleIPPConfig
+
+TESTS_ENVIRONMENT = perl $(top_srcdir)/test/runTest --verified=$(srcdir)/verified
+
+SampleIPPConfig: data/SampleIPPConfig
+	cp $? $@
+
+tests: $(TESTS) $(check_DATA)
+
+EXTRA_DIST = verified data
+
+CLEANFILES = $(TESTS) temp/* $(check_DATA) *~
+
+test: check
Index: /tags/RHL_v1_1/psModules/test/config/data/SampleIPPConfig
===================================================================
--- /tags/RHL_v1_1/psModules/test/config/data/SampleIPPConfig	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/config/data/SampleIPPConfig	(revision 9533)
@@ -0,0 +1,32 @@
+### Example .ipprc file
+
+### Database configuration
+DBSERVER	STR	ippdb.ifa.hawaii.edu	# Database host name (for psDBInit)
+DBUSER		STR	ipp			# Database user name (for psDBInit)
+DBPASSWORD	STR	password		# Database password (for psDBInit)
+
+### Setups for each camera system
+CAMERAS		METADATA
+	MEGACAM_RAW	STR	megacam_raw.config
+	MEGACAM_SPLICE	STR	megacam_splice.config
+	GPC1_RAW	STR	gpc1_raw.config
+	LRIS_BLUE	STR	lris_blue.config
+	LRIS_RED	STR	lris_red.config
+END
+
+### psLib setup
+#TIME		STR	/home/mithrandir/price/pan-starrs/jhroot/i686-pc-linux-gnu/etc/pslib/psTime.config	# Time configuration file
+LOGLEVEL	S32	3			# Logging level; 3=INFO
+LOGFORMAT	STR	HLNM			# Log format
+LOGDEST	STR	STDOUT				# Log destination
+TRACE		METADATA			# Trace levels
+	dummyTraceFunc01	S32	1
+	dummyTraceFunc02	S32	2
+END
+ARBITRARY_STRING_S32	S32	20
+ARBITRARY_STRING_F32	F32	21.0
+ARBITRARY_STRING_F64	F64	22.0
+ARBITRARY_STRING_STR	STR	19.0
+ARBITRARY_STRING_BOOL_T	BOOL	true
+ARBITRARY_STRING_BOOL_F	BOOL	false
+
Index: /tags/RHL_v1_1/psModules/test/config/tst_pmConfig.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/config/tst_pmConfig.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/config/tst_pmConfig.c	(revision 9533)
@@ -0,0 +1,277 @@
+/** @file tst_pmConfig.c
+ *
+ *  @brief Contains the tests for pmConfig.c:
+ *
+ * test00: This code will test the pmConfig() routine.
+ *
+ *  @author GLG, MHPCC
+ *
+ * XXX: Untested:
+ * pmConfigValidateCamera()
+ * pmConfigCameraFromHeader()
+ * pmConfigRecipeFromCamera()
+ * pmConfigDB()
+ *
+ * XXXX: Must determine what to do with NULL arguments, then test it.
+ *
+ *  @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-10 01:41:37 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include "psTest.h"
+#include "pslib.h"
+#include "pmConfig.h"
+
+static int test00(void);
+testDescription tests[] = {
+                              {test00, 000, "pmConfig:", true, false},
+                              {NULL}
+                          };
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    //
+    // We include the function names here in psTraceSetLevel() commands for
+    // debugging convenience.  There is no guarantee that this list of functions
+    // is complete.
+    //
+    psTraceSetLevel(".", 0);
+    psTraceSetLevel("readConfig", 0);
+    psTraceSetLevel("pmConfigRead", 0);
+    psTraceSetLevel("pmConfigValidateCamera", 0);
+    psTraceSetLevel("pmConfigCameraFromHeader", 0);
+    psTraceSetLevel("pmConfigRecipeFromCamera", 0);
+    psTraceSetLevel("pmConfigDB", 0);
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+/******************************************************************************
+test00():
+XXX: untested:
+    TIME:
+    LOGFORMAT: tested implicitly via the stdout files.
+    Command line arguments for logging shall override config file defaults.
+ *****************************************************************************/
+int test00( void )
+{
+    psTraceSetLevel(".", 0);
+    bool testStatus = true;
+    psBool rc = 0;
+    psString str[3];
+    str[0] = "ARGS:";
+    str[1] = "-site";
+    str[2] = "SampleIPPConfig";
+
+    psMetadata *site = psMetadataAlloc();
+    psMetadata *camera = psMetadataAlloc();
+    psMetadata *recipe = psMetadataAlloc();
+    psS32 argc = 3;
+
+
+    psMetadata *nullMD = NULL;
+    if (0) {
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with NULL site pointer.  Should generate ERROR, return false.\n");
+        rc = pmConfigRead(NULL, &camera, &recipe, &argc, str, "RecipeName");
+        if (rc == true) {
+            printf("TEST ERROR: pmConfigRead() returned true\n");
+            testStatus = false;
+        }
+
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with NULL *site pointer.  Should generate ERROR, return false.\n");
+        rc = pmConfigRead(&nullMD, &camera, &recipe, &argc, str, "RecipeName");
+        if (rc == true) {
+            printf("TEST ERROR: pmConfigRead() returned true\n");
+            testStatus = false;
+        }
+
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with NULL camera pointer.  Should generate ERROR, return false.\n");
+        rc = pmConfigRead(&site, NULL, &recipe, &argc, str, "RecipeName");
+        if (rc == true) {
+            printf("TEST ERROR: pmConfigRead() returned true\n");
+            testStatus = false;
+        }
+
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with NULL *camera pointer.  Should generate ERROR, return false.\n");
+        rc = pmConfigRead(&site, &nullMD, &recipe, &argc, str, "RecipeName");
+        if (rc == true) {
+            printf("TEST ERROR: pmConfigRead() returned true\n");
+            testStatus = false;
+        }
+
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with NULL recipe pointer.  Should generate ERROR, return false.\n");
+        rc = pmConfigRead(&site, &camera, NULL, &argc, str, "RecipeName");
+        if (rc == true) {
+            printf("TEST ERROR: pmConfigRead() returned true\n");
+            testStatus = false;
+        }
+
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with NULL *recipe pointer.  Should generate ERROR, return false.\n");
+        rc = pmConfigRead(&site, &camera, &nullMD, &argc, str, "RecipeName");
+        if (rc == true) {
+            printf("TEST ERROR: pmConfigRead() returned true\n");
+            testStatus = false;
+        }
+
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with NULL argv pointer.  Should generate ERROR, return false.\n");
+        rc = pmConfigRead(&site, &camera, &recipe, &argc, NULL, "RecipeName");
+        if (rc == true) {
+            printf("TEST ERROR: pmConfigRead() returned true\n");
+            testStatus = false;
+        }
+
+        printf("----------------------------------------------------------------\n");
+        printf("Calling pmConfigRead() with negative argc.  Should generate ERROR, return false.\n");
+        psS32 tmpArgc = -1;
+        rc = pmConfigRead(&site, &camera, &recipe, &tmpArgc, str, "RecipeName");
+        if (rc == true) {
+            printf("TEST ERROR: pmConfigRead() returned true\n");
+            testStatus = false;
+        }
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmConfigRead() with acceptable arguments.\n");
+
+    rc = pmConfigRead(&site, &camera, &recipe, &argc, str, "RecipeName");
+    if (rc == false) {
+        printf("TEST ERROR: pmConfigRead() returned False\n");
+        testStatus = false;
+    } else {
+        //
+        // Ensure that the various trace and logging values are set properly
+        //
+        if (1 != psTraceGetLevel("dummyTraceFunc01")) {
+            printf("TEST ERROR: failed to properly set tracelevel for dummyTraceFunc01\n");
+            printf("    tracelevel was %d, should be 1.\n", psTraceGetLevel("dummyTraceFunc01"));
+            testStatus = false;
+        }
+
+        if (2 != psTraceGetLevel("dummyTraceFunc02")) {
+            printf("TEST ERROR: failed to properly set tracelevel for dummyTraceFunc02\n");
+            printf("    tracelevel was %d, should be 2.\n", psTraceGetLevel("dummyTraceFunc02"));
+            testStatus = false;
+        }
+
+        if (1 != psLogGetDestination()) {
+            printf("TEST ERROR: failed to properly set log destination.\n");
+            printf("    it was %d, should be 1\n", psLogGetDestination());
+            testStatus = false;
+        }
+
+        if (3 != psLogGetLevel()) {
+            printf("TEST ERROR: failed to properly set log level.\n");
+            printf("    it was %d, should be 3\n", psLogGetLevel());
+            testStatus = false;
+        }
+
+        //
+        // Test the several arbitrary config file strings.
+        //
+        psS32 tmpInt = psMetadataLookupS32(&rc, site, "ARBITRARY_STRING_S32");
+        if ((rc == false) || (tmpInt != 20)) {
+            printf("TEST ERROR: failed to properly set metadata integer ARBITRARY_STRING_S32.\n");
+            testStatus = false;
+        }
+
+        psF32 tmpFloat = psMetadataLookupS32(&rc, site, "ARBITRARY_STRING_F32");
+        if ((rc == false) || (tmpFloat != 21.0)) {
+            printf("TEST ERROR: failed to properly set metadata float ARBITRARY_STRING_F32.\n");
+            testStatus = false;
+        }
+
+        psF64 tmpDub = psMetadataLookupS32(&rc, site, "ARBITRARY_STRING_F64");
+        if ((rc == false) || (tmpDub != 22.0)) {
+            printf("TEST ERROR: failed to properly set metadata double ARBITRARY_STRING_F64.\n");
+            testStatus = false;
+        }
+
+        psBool tmpBool;
+        tmpBool = psMetadataLookupS32(&rc, site, "ARBITRARY_STRING_BOOL_T");
+        if ((rc == false) || (tmpBool != true)) {
+            printf("TEST ERROR: failed to properly set metadata double ARBITRARY_STRING_BOOL_T.\n");
+            testStatus = false;
+        }
+        tmpBool = psMetadataLookupS32(&rc, site, "ARBITRARY_STRING_BOOL_F");
+        if ((rc == false) || (tmpBool != false)) {
+            printf("TEST ERROR: failed to properly set metadata double ARBITRARY_STRING_BOOL_F.\n");
+            testStatus = false;
+        }
+
+        //
+        // Test the database camera metadata keywords.
+        //
+        psMetadata *tmpMeta = psMetadataLookupMD(&rc, site, "CAMERAS");
+        if (rc == false) {
+            printf("TEST ERROR: failed to properly set metadata metadata CAMERAS.\n");
+            testStatus = false;
+        } else {
+            psString tmpStr;
+            tmpStr = psMetadataLookupStr(&rc, tmpMeta, "MEGACAM_RAW");
+            if ((rc == false) || (0 != strcmp(tmpStr, "megacam_raw.config"))) {
+                printf("TEST ERROR: failed to properly set metadata metadata CAMERAS->MEGACAM_RAW.\n");
+                testStatus = false;
+            }
+
+            tmpStr = psMetadataLookupStr(&rc, tmpMeta, "MEGACAM_SPLICE");
+            if ((rc == false) || (0 != strcmp(tmpStr, "megacam_splice.config"))) {
+                printf("TEST ERROR: failed to properly set metadata metadata CAMERAS->MEGACAM_SPLICE.\n");
+                testStatus = false;
+            }
+
+            tmpStr = psMetadataLookupStr(&rc, tmpMeta, "GPC1_RAW");
+            if ((rc == false) || (0 != strcmp(tmpStr, "gpc1_raw.config"))) {
+                printf("TEST ERROR: failed to properly set metadata metadata CAMERAS->GPC1_RAW.\n");
+                testStatus = false;
+            }
+
+            tmpStr = psMetadataLookupStr(&rc, tmpMeta, "LRIS_BLUE");
+            if ((rc == false) || (0 != strcmp(tmpStr, "lris_blue.config"))) {
+                printf("TEST ERROR: failed to properly set metadata metadata CAMERAS->LRIS_BLUE.\n");
+                testStatus = false;
+            }
+
+            tmpStr = psMetadataLookupStr(&rc, tmpMeta, "LRIS_RED");
+            if ((rc == false) || (0 != strcmp(tmpStr, "lris_red.config"))) {
+                printf("TEST ERROR: failed to properly set metadata metadata CAMERAS->LRIS_RED.\n");
+                testStatus = false;
+            }
+        }
+
+        //
+        // Test the database metadata keywords.  This is somewhat redundant since
+        // we already test metadat strings.
+        //
+        psString tmpStr;
+        tmpStr = psMetadataLookupStr(&rc, site, "DBSERVER");
+        if ((rc == false) || (0 != strcmp(tmpStr, "ippdb.ifa.hawaii.edu"))) {
+            printf("TEST ERROR: failed to properly set metadata string DBSERVER.\n");
+            testStatus = false;
+        }
+
+        tmpStr = psMetadataLookupStr(&rc, site, "DBUSER");
+        if ((rc == false) || (0 != strcmp(tmpStr, "ipp"))) {
+            printf("TEST ERROR: failed to properly set metadata string DBUSER.\n");
+            testStatus = false;
+        }
+
+        tmpStr = psMetadataLookupStr(&rc, site, "DBPASSWORD");
+        if ((rc == false) || (0 != strcmp(tmpStr, "password"))) {
+            printf("TEST ERROR: failed to properly set metadata string DBPASSWORD.\n");
+            testStatus = false;
+        }
+    }
+
+    psFree(site);
+    psFree(camera);
+    psFree(recipe);
+    return(testStatus);
+}
Index: /tags/RHL_v1_1/psModules/test/config/verified/tst_pmConfig.stderr
===================================================================
--- /tags/RHL_v1_1/psModules/test/config/verified/tst_pmConfig.stderr	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/config/verified/tst_pmConfig.stderr	(revision 9533)
@@ -0,0 +1,11 @@
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmConfig.c                                             *
+*            TestPoint: Test Point Driver{pmConfig:}                               *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|I|readConfig
+    Loading site configuration from file SampleIPPConfig
+
+---> TESTPOINT PASSED (Test Point Driver{pmConfig:} | tst_pmConfig.c)
+
Index: /tags/RHL_v1_1/psModules/test/config/verified/tst_pmConfig.stdout
===================================================================
--- /tags/RHL_v1_1/psModules/test/config/verified/tst_pmConfig.stdout	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/config/verified/tst_pmConfig.stdout	(revision 9533)
@@ -0,0 +1,2 @@
+----------------------------------------------------------------
+Calling pmConfigRead() with acceptable arguments.
Index: /tags/RHL_v1_1/psModules/test/detrend/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/detrend/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/detrend/.cvsignore	(revision 9533)
@@ -0,0 +1,9 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmFlatField
+tst_pmMaskBadPixels
+tst_pmNonLinear
+.tmp_tst_pmNonLinearLookupFile
Index: /tags/RHL_v1_1/psModules/test/detrend/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/detrend/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/detrend/Makefile.am	(revision 9533)
@@ -0,0 +1,15 @@
+AM_CPPFLAGS = \
+	$(SRCINC) \
+	-I$(top_srcdir)/test/tap/src \
+	$(PSMODULES_CFLAGS)
+
+AM_LDFLAGS = \
+	$(top_builddir)/src/libpsmodules.la  \
+	$(top_builddir)/test/tap/src/libtap.la \
+	$(PSMODULES_LIBS)
+
+check_PROGRAMS = tap_pmShutterCorrection
+
+CLEANFILES = temp/* core core.* *~
+
+test: check
Index: /tags/RHL_v1_1/psModules/test/detrend/tap_pmShutterCorrection.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/detrend/tap_pmShutterCorrection.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/detrend/tap_pmShutterCorrection.c	(revision 9533)
@@ -0,0 +1,430 @@
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "tap.h"
+
+int main (void)
+{
+    plan_tests(143);
+
+    diag("pmShutterCorrection tests");
+
+    // test allocation, free of pmShutterCorrPars
+    diag("pmShutterCorrParsAlloc tests");
+    {
+        psMemId id = psMemGetId();
+        pmShutterCorrPars *pars = pmShutterCorrParsAlloc ();
+
+        ok(pars != NULL, "pmShutterCorrPars successfully allocated");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrParsAlloc() failed");
+        skip_end();
+
+        psFree(pars);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test parameter guess (linearly spaced exptimes, TK/TO < 1)
+    diag("pmShutterCorrectionGuess tests : coarse linear-spaced exptimes");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 10;
+        float AK = 5.0;
+        float TK = 0.1;
+        float TO = 0.2;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = i*0.25;
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrPars *pars = pmShutterCorrectionGuess (exptime, counts);
+
+        ok(pars != NULL, "pmShutterCorrPars successfully allocated");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrParsAlloc() failed");
+
+        // with coarse linearly-spaced times large compared to TO and TK,
+        // we can't expect very accurate guesses.  the exptime guess is fairly good because
+        // the largest exptime is much longer than TK or TO
+        ok(fabs(pars->scale  - AK) < 0.5, "scale guess is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.5, "offset guess is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.5, "offref guess is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        psFree(exptime);
+        psFree(counts);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test parameter guess (linearly spaced exptimes, TK/TO < 1)
+    diag("pmShutterCorrectionGuess tests : fine linear-spaced exptimes");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 20;
+        float AK = 5.0;
+        float TK = 0.1;
+        float TO = 0.2;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = i*0.1;
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrPars *pars = pmShutterCorrectionGuess (exptime, counts);
+
+        ok(pars != NULL, "pmShutterCorrPars successfully allocated");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrParsAlloc() failed");
+
+        // with fine linearly-spaced times large compared to TO and TK,
+        // we get a good guess to TK and TO, but since the largest exptime is not
+        // many times larger than TO, we don't do very well with the AK
+        ok(fabs(pars->scale  - AK) < 0.5, "scale guess is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.05, "offset guess is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.05, "offref guess is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        psFree(exptime);
+        psFree(counts);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test parameter guess (log spaced exptimes, TK/TO < 1)
+    diag("pmShutterCorrectionGuess tests : log-spaced exptimes");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 40;
+        float AK = 5.0;
+        float TK = 0.1;
+        float TO = 0.2;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = pow(10.0, -2 + i*0.1);
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrPars *pars = pmShutterCorrectionGuess (exptime, counts);
+
+        // with fine log-spaced times well-sampling TO and TK,
+        // we can expect accurate guesses
+        ok(pars != NULL, "pmShutterCorrPars successfully allocated");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrParsAlloc() failed");
+        ok(fabs(pars->scale  - AK) < 0.01, "scale guess is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.01, "offset guess is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.01, "offref guess is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        psFree(exptime);
+        psFree(counts);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+
+    // test parameter guess (linearly spaced exptimes, TK/TO > 1)
+    diag("pmShutterCorrectionGuess tests : coarse linear-spaced exptimes, TK/TO > 1");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 10;
+        float AK = 5.0;
+        float TK = 0.2;
+        float TO = 0.1;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = i*0.25;
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrPars *pars = pmShutterCorrectionGuess (exptime, counts);
+
+        ok(pars != NULL, "pmShutterCorrPars successfully allocated");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrParsAlloc() failed");
+
+        // with coarse linearly-spaced times large compared to TO and TK,
+        // we can't expect very accurate guesses.  the exptime guess is fairly good because
+        // the largest exptime is much longer than TK or TO
+        ok(fabs(pars->scale  - AK) < 0.5, "scale guess is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.5, "offset guess is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.5, "offref guess is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        psFree(exptime);
+        psFree(counts);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test parameter guess (linearly spaced exptimes, TK/TO > 1)
+    diag("pmShutterCorrectionGuess tests : fine linear-spaced exptimes, TK/TO > 1");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 20;
+        float AK = 5.0;
+        float TK = 0.2;
+        float TO = 0.1;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = i*0.1;
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrPars *pars = pmShutterCorrectionGuess (exptime, counts);
+
+        ok(pars != NULL, "pmShutterCorrPars successfully allocated");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrParsAlloc() failed");
+
+        // with fine linearly-spaced times large compared to TO and TK,
+        // we get a good guess to TK and TO, but since the largest exptime is not
+        // many times larger than TO, we don't do very well with the AK
+        ok(fabs(pars->scale  - AK) < 0.5, "scale guess is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.05, "offset guess is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.05, "offref guess is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        psFree(exptime);
+        psFree(counts);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test parameter guess (log spaced exptimes, TK/TO > 1)
+    diag("pmShutterCorrectionGuess tests : log-spaced exptimes, TK/TO > 1");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 40;
+        float AK = 5.0;
+        float TK = 0.2;
+        float TO = 0.1;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = pow(10.0, -2 + i*0.1);
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrPars *pars = pmShutterCorrectionGuess (exptime, counts);
+
+        // with fine log-spaced times well-sampling TO and TK,
+        // we can expect accurate guesses
+        ok(pars != NULL, "pmShutterCorrPars successfully allocated");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrParsAlloc() failed");
+        ok(fabs(pars->scale  - AK) < 0.01, "scale guess is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.01, "offset guess is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.01, "offref guess is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        psFree(exptime);
+        psFree(counts);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test non-linear fitting
+    diag("pmShutterCorrectionFullFit tests : linear-spaced exptimes");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 20;
+        float FL = 10000.0;
+        float AK = 5.0;
+        float TK = 0.2;
+        float TO = 0.1;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *cntErr  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = cntErr->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = i*0.1;
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+            cntErr->data.F32[i] = AK*sqrt(FL*(exptime->data.F32[i] + TK)) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrPars *guess = pmShutterCorrectionGuess (exptime, counts);
+        skip_start(guess == NULL, 0, "Skipping tests because pmShutterCorrectionGuess() failed");
+        pmShutterCorrPars *pars = pmShutterCorrectionFullFit (exptime, counts, cntErr, guess);
+
+        // with fine log-spaced times well-sampling TO and TK,
+        // we can expect accurate guesses
+        ok(pars != NULL, "pmShutterCorrPars successfully allocated by FullFit");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrParsAlloc() failed");
+        ok(fabs(pars->scale  - AK) < 0.01, "scale fit is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.01, "offset fit is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.01, "offref fit is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        skip_end();
+
+        psFree(guess);
+        psFree(exptime);
+        psFree(counts);
+        psFree(cntErr);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test non-linear fitting
+    diag("pmShutterCorrectionFullFit tests : log-spaced exptimes");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 40;
+        float FL = 10000.0;
+        float AK = 1.0;
+        float TK = 0.2;
+        float TO = 0.1;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *cntErr  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = cntErr->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = pow(10.0, -2 + i*0.1);
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+            cntErr->data.F32[i] = AK*sqrt(FL*(exptime->data.F32[i] + TK)) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrPars *guess = pmShutterCorrectionGuess (exptime, counts);
+        skip_start(guess == NULL, 0, "Skipping tests because pmShutterCorrectionGuess() failed");
+        pmShutterCorrPars *pars = pmShutterCorrectionFullFit (exptime, counts, cntErr, guess);
+
+        // with fine log-spaced times well-sampling TO and TK,
+        // we can expect accurate guesses
+        ok(pars != NULL, "pmShutterCorrPars successfully allocated by FullFit");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrParsAlloc() failed");
+        ok(fabs(pars->scale  - AK) < 0.01, "scale fit is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.01, "offset fit is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.01, "offref fit is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        skip_end();
+
+        psFree(guess);
+        psFree(exptime);
+        psFree(counts);
+        psFree(cntErr);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // XXX should add tests with the input counts scattered with GaussDev...
+
+    // test linear fitting
+    diag("pmShutterCorrectionLinFit tests : linear-spaced exptimes");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 20;
+        float FL = 10000.0;
+        float AK = 5.0;
+        float TK = 0.2;
+        float TO = 0.1;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *cntErr  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = cntErr->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = i*0.1;
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+            cntErr->data.F32[i] = AK*sqrt(FL*(exptime->data.F32[i] + TK)) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrPars *guess = pmShutterCorrectionGuess (exptime, counts);
+        skip_start(guess == NULL, 0, "Skipping tests because pmShutterCorrectionGuess() failed");
+        pmShutterCorrPars *full = pmShutterCorrectionFullFit (exptime, counts, cntErr, guess);
+        pmShutterCorrPars *pars = pmShutterCorrectionLinFit (exptime, counts, cntErr, full->offref);
+
+        // with fine log-spaced times well-sampling TO and TK,
+        // we can expect accurate guesses
+        ok(pars != NULL, "pmShutterCorrPars successfully allocated by FullFit");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrParsAlloc() failed");
+        ok(fabs(pars->scale  - AK) < 0.01, "scale fit is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.01, "offset fit is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.01, "offref fit is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        psFree(full);
+        skip_end();
+
+        psFree(guess);
+        psFree(exptime);
+        psFree(counts);
+        psFree(cntErr);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    // test linear fitting
+    diag("pmShutterCorrectionLinFit tests : log-spaced exptimes");
+    {
+        psMemId id = psMemGetId();
+
+        int NPTS = 40;
+        float FL = 10000.0;
+        float AK = 1.0;
+        float TK = 0.2;
+        float TO = 0.1;
+        psVector *exptime = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *counts  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        psVector *cntErr  = psVectorAlloc (NPTS, PS_TYPE_F32);
+        exptime->n = counts->n = cntErr->n = NPTS;
+
+        for (int i = 0; i < exptime->n; i++) {
+            exptime->data.F32[i] = pow(10.0, -2 + i*0.1);
+            counts->data.F32[i] = AK*(exptime->data.F32[i] + TK) / (exptime->data.F32[i] + TO);
+            cntErr->data.F32[i] = AK*sqrt(FL*(exptime->data.F32[i] + TK)) / (exptime->data.F32[i] + TO);
+        }
+
+        pmShutterCorrPars *guess = pmShutterCorrectionGuess (exptime, counts);
+        skip_start(guess == NULL, 0, "Skipping tests because pmShutterCorrectionGuess() failed");
+        pmShutterCorrPars *full = pmShutterCorrectionFullFit (exptime, counts, cntErr, guess);
+        pmShutterCorrPars *pars = pmShutterCorrectionLinFit (exptime, counts, cntErr, full->offref);
+
+        // with fine log-spaced times well-sampling TO and TK,
+        // we can expect accurate guesses
+        ok(pars != NULL, "pmShutterCorrPars successfully allocated by FullFit");
+        skip_start(pars == NULL, 0, "Skipping tests because pmShutterCorrParsAlloc() failed");
+        ok(fabs(pars->scale  - AK) < 0.01, "scale fit is close enough (got %f vs %f)",  pars->scale, AK);
+        ok(fabs(pars->offset - TK) < 0.01, "offset fit is close enough (got %f vs %f)", pars->offset, TK);
+        ok(fabs(pars->offref - TO) < 0.01, "offref fit is close enough (got %f vs %f)", pars->offref, TO);
+        skip_end();
+
+        psFree(pars);
+        psFree(full);
+        skip_end();
+
+        psFree(guess);
+        psFree(exptime);
+        psFree(counts);
+        psFree(cntErr);
+        ok(!psMemCheckLeaks (id, NULL, NULL, false), "no memory leaks");
+    }
+
+    return exit_status();
+}
Index: /tags/RHL_v1_1/psModules/test/detrend/tst_pmFlatField.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/detrend/tst_pmFlatField.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/detrend/tst_pmFlatField.c	(revision 9533)
@@ -0,0 +1,290 @@
+/** @file tst_pmFlatField.c
+ *
+ *  @brief Contains the tests for pmFlatField.c:
+ *
+ *    Test A - Divide input image by flat image
+ *    Test B - Mask flat image data
+ *    Test C - Mask flat image data starting with non-null mask
+ *    Test E - Attempt to use null input image
+ *    Test F - Attempt tp use null flat image
+ *    Test G - Attempt to use input image bigger than flat image
+ *    Test H - Attempt to use input image mask bigger than flat image
+ *    Test I - Attempt to use offset greater than input image
+ *    Test J - Attempt to use complex input image
+ *    Test K - Attempt to use complex flat image
+ *    Test L - Attempt to use non-equal input and flat image types
+ *    Test M - Attempt to use non-mask type mask image
+ *
+ * XXX: Added a mask argument to pmFlatField().  Must add tests.  For now, all
+ * masks are NULL.
+ *
+ *  @author Ross Harman, MHPCC
+ *
+ *  XXX: I added the CELL.TRIMSEC region code but there are not tests for it.
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2005-11-15 20:09:03 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+
+#include "psTest.h"
+#include "pslib.h"
+#include "pmFlatField.h"
+
+
+#define PRINT_MATRIX(IMAGE,TYPE,STRING)                                                                      \
+printf(STRING);                                                                                              \
+printf("\n");                                                                                                \
+for(int i=IMAGE->numRows-1; i>-1; i--) {                                                                     \
+    for(int j=0; j<IMAGE->numCols; j++) {                                                                    \
+        if(PS_IS_PSELEMTYPE_COMPLEX(IMAGE->type.type)) {                                                     \
+            printf("%f+%fi ", creal(IMAGE->data.TYPE[i][j]), cimag(IMAGE->data.TYPE[i][j]));                 \
+        } else if(PS_IS_PSELEMTYPE_INT(IMAGE->type.type)) {                                                  \
+            printf("%d ", (int)IMAGE->data.TYPE[i][j]);                                                      \
+        } else {                                                                                             \
+            printf("%f ", (double)IMAGE->data.TYPE[i][j]);                                                   \
+        }                                                                                                    \
+    }                                                                                                        \
+    printf("\n");                                                                                            \
+}                                                                                                            \
+printf("\n");
+
+
+#define CREATE_AND_SET_IMAGE(NAME,TYPE,VALUE,NROWS,NCOLS)                                                    \
+psImage *NAME = (psImage*)psImageAlloc(NCOLS,NROWS,PS_TYPE_##TYPE);                                          \
+for(int i=0; i<NAME->numRows; i++) {                                                                         \
+    for(int j=0; j<NAME->numCols; j++) {                                                                     \
+        NAME->data.TYPE[i][j] = VALUE;                                                                       \
+    }                                                                                                        \
+}
+
+
+static int testFlatField(void);
+
+
+testDescription tests[] = {
+                              {testFlatField, 753, "pmFlatField", 0, false},
+                              {NULL}
+                          };
+
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+
+int testFlatField( void )
+{
+    // Test A - Divide input image by flat image
+    printPositiveTestHeader(stdout, "pmFlatField", "Test A - Divide input image by flat image");
+    CREATE_AND_SET_IMAGE(inImage,F64,6.0,3,3)
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    inReadout->image = inImage;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+    CREATE_AND_SET_IMAGE(inMask, U8, 0, 3,3);
+    inReadout->mask = inMask;
+    PRINT_MATRIX((inReadout->mask),U8,"Input mask:");
+    PRINT_MATRIX(inImage,F64,"Input image:");
+
+    CREATE_AND_SET_IMAGE(flatImage1,F64,2.0,3,3)
+    pmReadout *flatReadout = pmReadoutAlloc(NULL);
+    flatReadout->row0 = 0;
+    flatReadout->col0 = 0;
+    flatReadout->image = flatImage1;
+    PRINT_MATRIX(flatImage1,F64,"Flat image:");
+
+    if ( !pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test A - Returned false should be true");
+        return 1;
+    }
+    PRINT_MATRIX(inImage,F64, "Resulting image:");
+    printFooter(stdout, "pmFlatField", "Test A - Divide input image by flat image", true);
+    printf("\n\n\n");
+
+
+    // Test B - Mask flat image data
+    printPositiveTestHeader(stdout, "pmFlatField", "Test B - Mask flat image data");
+    PRINT_MATRIX(inImage, F64, "Input image:");
+    CREATE_AND_SET_IMAGE(flatImage2,F64,0.0,3,3)
+    PRINT_MATRIX(flatImage2, F64, "Flat image:");
+    flatReadout->image = flatImage2;
+    if ( !pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test B - Returned false should be true");
+        return 2;
+    }
+    PRINT_MATRIX(inReadout->mask, PS_TYPE_MASK_DATA, "Resulting mask:");
+    PRINT_MATRIX(inImage,F64,"Resulting image:");
+    printFooter(stdout, "pmFlatField", "Test B - Mask flat image data", true);
+    printf("\n\n\n");
+
+
+    // Test C - Mask flat image data starting with non-null mask
+    printPositiveTestHeader(stdout, "pmFlatField", "Test C - Mask flat image data starting with non-null mask");
+    PRINT_MATRIX(inImage, F64, "Input image:");
+    flatImage2->data.F64[0][0] = 3.0;
+    flatImage2->data.F64[0][1] = -3.0;
+    PRINT_MATRIX(flatImage2, F64, "Flat image:");
+    CREATE_AND_SET_IMAGE(mask1,U8,0,3,3);
+    psFree(inReadout->mask);
+    inReadout->mask = mask1;
+    if ( !pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test C - Returned false should be true");
+        return 3;
+    }
+    PRINT_MATRIX(flatImage2, F64, "Flat image out:");
+    PRINT_MATRIX(inReadout->mask, PS_TYPE_MASK_DATA,"Resulting mask:");
+    PRINT_MATRIX(inImage,F64,"Resulting image:");
+    printFooter(stdout, "pmFlatField", "Test C - Mask flat image data starting with non-null mask", true);
+    printf("\n\n\n");
+
+
+    // Test D - Attempt to use null flat readout
+    printNegativeTestHeader(stdout,"pmFlatField", "Test D - Attempt to use null flat readout",
+                            "Null not allowed for flat readout", 0);
+    if( pmFlatField(inReadout, NULL) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test D - Returned true should be false");
+        return 4;
+    }
+    printFooter(stdout, "pmFlatField", "Test D - Attempt to use null flat readout", true);
+    printf("\n\n\n");
+
+
+    // Test E - Attempt to use null input image
+    printNegativeTestHeader(stdout,"pmFlatField", "Test E - Attempt to use null input image",
+                            "Null not allowed for input image", 0);
+    psImage *temp = inReadout->image;
+    inReadout->image = NULL;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test E - Returned true should be false" );
+        return 5;
+    }
+    inReadout->image = temp    ;
+    printFooter(stdout, "pmFlatField", "Test E - Attempt to use null input image", true);
+    printf("\n\n\n");
+
+
+    // Test F - Attempt tp use null flat image
+    printNegativeTestHeader(stdout,"pmFlatField", "Test F - Attempt tp use null flat image",
+                            "Null not allowed for flat image", 0);
+    temp = flatReadout->image;
+    flatReadout->image = NULL;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test F - Returned true should be false" );
+        return 6;
+    }
+    flatReadout->image = temp;
+    printFooter(stdout, "pmFlatField", "Test F - Attempt tp use null flat image", true);
+    printf("\n\n\n");
+
+
+    // Test G - Attempt to use input image bigger than flat image
+    printNegativeTestHeader(stdout,"pmFlatField", "Test G - Attempt to use input image bigger than flat image",
+                            "Input image size exceeds that of flat image", 0);
+    CREATE_AND_SET_IMAGE(smallFlat,F64,0.0,2,2);
+    temp = flatReadout->image;
+    flatReadout->image = smallFlat;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test G - Returned true should be false");
+        return 7;
+    }
+    flatReadout->image = temp;
+    printFooter(stdout, "pmFlatField", "Test G - Attempt to use input image bigger than flat image", true);
+    printf("\n\n\n");
+
+    // Test H - Attempt to use input image mask bigger than flat image
+    printNegativeTestHeader(stdout,"pmFlatField", "Test H - Attempt to use input image mask bigger than flat image",
+                            "Input image mask size exceeds that of flat image", 0);
+    CREATE_AND_SET_IMAGE(largeMask,F64,0.0,5,5);
+    inReadout->mask = largeMask;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test H - Returned true should be false");
+        return 8;
+    }
+    printFooter(stdout, "pmFlatField", "Test H - Attempt to use input image mask bigger than flat image", true);
+    printf("\n\n\n");
+    inReadout->mask = mask1;
+
+    // Test I - Attempt to use offset greater than input image
+    printNegativeTestHeader(stdout,"pmFlatField", "Test I - Attempt to use offset greater than input image",
+                            "Total offset >= input image size", 0);
+    *(int*)&inReadout->col0 = 50;
+    *(int*)&inReadout->row0 = 50;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test I - Returned true should be false");
+        return 9;
+    }
+    *(int*)&inReadout->col0 = 0;
+    *(int*)&inReadout->row0 = 0;
+    printFooter(stdout, "pmFlatField", "Test I - Attempt to use offset greater than input image", true);
+    printf("\n\n\n");
+
+
+    // Test J - Attempt to use complex input image
+    printNegativeTestHeader(stdout,"pmFlatField", "Test J - Attempt to use complex input image",
+                            "Complex types not allowed for input image", 0);
+    *(psElemType* ) & inReadout->image->type.type = PS_TYPE_C64;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test J - Returned true should be false");
+        return 10;
+    }
+    *(psElemType* ) & inReadout->image->type.type = PS_TYPE_F64;
+    printFooter(stdout, "pmFlatField", "Test J - Attempt to use complex input image", true);
+    printf("\n\n\n");
+
+
+    // Test K - Attempt to use complex flat image
+    printNegativeTestHeader(stdout,"pmFlatField", "Test K - Attempt to use complex flat image",
+                            "Complex types not allowed for flat image", 0);
+    *(psElemType* ) & flatReadout->image->type.type = PS_TYPE_C64;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test K - Returned ture should be false");
+        return 11;
+    }
+    *(psElemType* ) & flatReadout->image->type.type = PS_TYPE_F64;
+    printFooter(stdout, "pmFlatField", "Test K - Attempt to use complex flat image", true);
+    printf("\n\n\n");
+
+
+    // Test L - Attempt to use non-equal input and flat image types
+    printNegativeTestHeader(stdout,"pmFlatField", "Test L - Attempt to use non-equal input and flat image types",
+                            "Input and flat image types differ", 0);
+    *(psElemType* ) & flatReadout->image->type.type = PS_TYPE_F32;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test L - Returned true should be false");
+        return 12;
+    }
+    *(psElemType* ) & flatReadout->image->type.type = PS_TYPE_F64;
+    printFooter(stdout, "pmFlatField", "Test L - Attempt to use non-equal input and flat image types", true);
+    printf("\n\n\n");
+
+
+    // Test M - Attempt to use non-mask type mask image
+    printNegativeTestHeader(stdout,"pmFlatField", "Test M - Attempt to use non-mask type mask image",
+                            "Mask must be PS_TYPE_MASK type", 0);
+    *(psElemType* ) & inReadout->mask->type.type = PS_TYPE_F32;
+    if ( pmFlatField(inReadout, flatReadout) ) {
+        psError(PS_ERR_UNKNOWN,true,"Test M - Returned true should be false");
+        return 13;
+    }
+    *(psElemType* ) & inReadout->mask->type.type = PS_TYPE_MASK;
+    printFooter(stdout, "pmFlatField", "Test M - Attempt to use non-mask type mask image", true);
+    printf("\n\n\n");
+
+
+    // Free memory
+    psFree(inReadout);
+    psFree(flatReadout);
+    //psFree(inImage);
+    psFree(flatImage1);
+    //psFree(flatImage1);
+    psFree(smallFlat);
+    psFree(largeMask);
+
+    return 0;
+}
+
Index: /tags/RHL_v1_1/psModules/test/detrend/tst_pmMaskBadPixels.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/detrend/tst_pmMaskBadPixels.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/detrend/tst_pmMaskBadPixels.c	(revision 9533)
@@ -0,0 +1,381 @@
+/** @file tst_pmMaskBadPixels.c
+ *
+ *  @brief Contains the tests for pmMaskBadPixels:
+ *
+ *    Test A - Create mask based on maskVal argument
+ *    Test B - Create mask based on saturation argument
+ *    Test C - Create mask based on growVal and grow arguments
+ *    Test D - Auto Create mask based on maskVal argument
+ *    Test E - Attempt to use null mask
+ *    Test F - Attempt tp use null input image
+ *    Test G - Attempt to use input image bigger than mask
+ *    Test H - Attempt to use input image mask bigger than mask
+ *    Test I - Attempt to use offset greater than input image
+ *    Test J - Attempt to use complex input image
+ *    Test K - Attempt to use non-mask type mask image
+ *
+ *  @author Ross Harman, MHPCC
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2005-11-15 20:09:03 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+
+#include "psTest.h"
+#include "pslib.h"
+#include "pmMaskBadPixels.h"
+
+
+#define PRINT_MATRIX(IMAGE,TYPE,STRING)                                                                      \
+printf(STRING);                                                                                              \
+printf("\n");                                                                                                \
+for(int i=(IMAGE)->numRows-1; i>-1; i--) {                                                                     \
+    for(int j=0; j<(IMAGE)->numCols; j++) {                                                                    \
+        if(PS_IS_PSELEMTYPE_COMPLEX((IMAGE)->type.type)) {                                                     \
+            printf("%f+%fi ", creal((IMAGE)->data.TYPE[i][j]), cimag((IMAGE)->data.TYPE[i][j]));                 \
+        } else if(PS_IS_PSELEMTYPE_INT((IMAGE)->type.type)) {                                                  \
+            printf("%d", (int)(IMAGE)->data.TYPE[i][j]);                                                       \
+        } else {                                                                                             \
+            printf("%f", (double)(IMAGE)->data.TYPE[i][j]);                                                    \
+        }                                                                                                    \
+    }                                                                                                        \
+    printf("\n");                                                                                            \
+}                                                                                                            \
+printf("\n");
+
+
+#define CREATE_AND_SET_IMAGE(NAME,TYPE,VALUE,NROWS,NCOLS)                                                    \
+(NAME) = (psImage*)psImageAlloc(NCOLS,NROWS,PS_TYPE_##TYPE);                                             \
+for(int i=0; i<(NAME)->numRows; i++) {                                                                         \
+    for(int j=0; j<(NAME)->numCols; j++) {                                                                     \
+        (NAME)->data.TYPE[i][j] = VALUE;                                                                       \
+    }                                                                                                        \
+}
+
+static int testMaskBadPixels1(void);
+static int testMaskBadPixels2(void);
+static int testMaskBadPixels3(void);
+static int testMaskBadPixels4(void);
+static int testMaskBadPixels5(void);
+static int testMaskBadPixels6(void);
+static int testMaskBadPixels7(void);
+static int testMaskBadPixels8(void);
+static int testMaskBadPixels9(void);
+static int testMaskBadPixels10(void);
+static int testMaskBadPixels11(void);
+
+
+testDescription tests[] = {
+                              {testMaskBadPixels1, 885, "pmMaskBadPixels - Create mask based on maskVal argument", 0, false},
+                              {testMaskBadPixels2, 885, "pmMaskBadPixels - Create mask based on saturation argument", 0, false},
+                              {testMaskBadPixels3, 885, "pmMaskBadPixels - Create mask based on growVal and grow arguments", 0, false},
+                              {testMaskBadPixels4, 885, "pmMaskBadPixels - Auto create mask based on maskVal argument", 0, false},
+                              {testMaskBadPixels5, 885, "pmMaskBadPixels - Attempt to use null mask", 0, false},
+                              {testMaskBadPixels6, 885, "pmMaskBadPixels - Attempt tp use null input image", 0, false},
+                              {testMaskBadPixels7, 885, "pmMaskBadPixels - Attempt to use input image bigger than mask", 0, false},
+                              {testMaskBadPixels8, 885, "pmMaskBadPixels - Attempt to use input image mask bigger than mask", 0, false},
+                              {testMaskBadPixels9, 885, "pmMaskBadPixels - Attempt to use offset greater than input image", 0, false},
+                              {testMaskBadPixels10, 885, "pmMaskBadPixels - Attempt to use complex input image", 0, false},
+                              {testMaskBadPixels11, 885, "pmMaskBadPixels - Attempt to use non-mask type mask image", 0, false        },
+                              {NULL}
+                          };
+
+/*
+    #define PS_TYPE_MASK PS_TYPE_U8
+    #define PS_TYPE_MASK_DATA U8
+    #define PS_TYPE_MASK_NAME "psU8"
+*/
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+
+#define NUM_ROWS 50
+#define NUM_COLS 50
+#define DEFAULT_IMAGE_VAL 0.0
+#define DEFAULT_MASK_VAL 0
+#define MASK_VAL 1
+#define SAT_VAL  100.0
+#define GROW_VAL 1
+#define GROW_RAD 10
+int testMaskBadPixels1( void )
+{
+    //
+    // Test A - Create mask based on maskVal argument
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS)
+    maskReadout->image->data.PS_TYPE_MASK_DATA[NUM_ROWS/2][NUM_COLS/2]=1;
+    maskReadout->image->row0 = 0;
+    maskReadout->image->col0 = 0;
+
+    PRINT_MATRIX(maskReadout->image, U8, "Data mask:");
+
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+    PRINT_MATRIX(inReadout->mask, PS_TYPE_MASK_DATA, "Resulting mask:");
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels2( void )
+{
+    //
+    // Test B - Create mask based on saturation argument
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+    inReadout->image->data.F32[NUM_ROWS/2][NUM_COLS/2] = 150.0;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS)
+
+    //PS_IMAGE_PRINT_F32(inReadout->image);
+    PRINT_MATRIX(maskReadout->image, U8, "Data mask:");
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+    PRINT_MATRIX(inReadout->mask, U8, "Resulting mask:");
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels3( void )
+{
+    //
+    // Test C - Create mask based on growVal and grow arguments
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+    inReadout->image->data.F32[NUM_ROWS/2][NUM_COLS/2]=GROW_VAL;
+    inReadout->image->data.F32[NUM_ROWS/4][NUM_COLS/4]=GROW_VAL;
+    inReadout->image->data.F32[NUM_ROWS/4][NUM_COLS-(NUM_COLS/4)]=GROW_VAL;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS);
+
+    PRINT_MATRIX(maskReadout->image, U8, "Data mask:");
+    //PS_IMAGE_PRINT_F32(inReadout->image);
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+    PRINT_MATRIX(inReadout->mask, U8, "Resulting mask:");
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels4( void )
+{
+    //
+    // Test D - Auto Create mask based on maskVal argument
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS)
+    maskReadout->image->data.PS_TYPE_MASK_DATA[NUM_ROWS/2][NUM_COLS/2]=1;
+
+    PRINT_MATRIX(maskReadout->image, U8, "Data mask:");
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+    PRINT_MATRIX(inReadout->mask, U8, "Resulting mask:");
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels5( void )
+{
+    //
+    // Test E - Attempt to use null mask
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+
+    pmMaskBadPixels(inReadout, NULL, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+    psFree(inReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels6( void )
+{
+    //
+    // Test F - Attempt tp use null input image
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS)
+
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels7( void )
+{
+    //
+    // Test G - Attempt to use input image bigger than mask
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS+10, NUM_COLS+10);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS)
+
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels8( void )
+{
+    //
+    // Test H - Attempt to use mask bigger than image
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS+10, NUM_COLS+10)
+
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels9( void )
+{
+    //
+    // Test I - Attempt to use offset greater than input image
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+    *(int*)&inReadout->image->col0 = 150;
+    *(int*)&inReadout->image->row0 = 150;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS)
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels10( void )
+{
+    //
+    // Test J - Attempt to use complex input image
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, C64, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, U8, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS)
+
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
+int testMaskBadPixels11( void )
+{
+    //
+    // Test K - Attempt to use mask image with wrong data type.
+    //
+
+    pmReadout *inReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(inReadout->image, F32, DEFAULT_IMAGE_VAL, NUM_ROWS, NUM_COLS);
+    inReadout->image->row0 = 0;
+    inReadout->image->col0 = 0;
+    inReadout->row0 = 0;
+    inReadout->col0 = 0;
+
+    pmReadout *maskReadout = pmReadoutAlloc(NULL);
+    CREATE_AND_SET_IMAGE(maskReadout->image, F64, DEFAULT_MASK_VAL, NUM_ROWS, NUM_COLS)
+
+    pmMaskBadPixels(inReadout, maskReadout, MASK_VAL, SAT_VAL, GROW_VAL, GROW_RAD);
+
+    psFree(inReadout);
+    psFree(maskReadout);
+
+    return 0;
+}
+
Index: /tags/RHL_v1_1/psModules/test/detrend/tst_pmNonLinear.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/detrend/tst_pmNonLinear.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/detrend/tst_pmNonLinear.c	(revision 9533)
@@ -0,0 +1,268 @@
+/* @file tst_pmNonLinear.c
+ *
+ *  @brief Contains the tests for pmNonLinear.c:
+ *
+ * test00: This code will create a simple polynomial, and call
+ * pmNonLinearityPolynomial() for a variety of image sizes [(1, 1), (1,
+ * N), (N, 1), (N, N)].  
+ *
+ * test01: This code will create simple table lookup vectors, and call
+ * pmNonLinearityPolynomial() for a variety of image sizes [(1, 1), (1,
+ * N), (N, 1), (N, N)].  
+ *
+ * test02, test03: This code tests the functions with various unallowable
+ * input parameters (NULLS) and incorrect vector sizes.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  XXX: Add tests in which the lookup file has incorrect number of entries,
+ *  and where the data is outside the pmReadout range.
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-01-26 21:10:51 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#include "psTest.h"
+#include "pslib.h"
+#include "pmNonLinear.h"
+static int test00(void);
+static int test01(void);
+static int test02(void);
+static int test03(void);
+testDescription tests[] = {
+                              {test00, 000, "pmNonLinearityPolynomial", true, false},
+                              {test01, 000, "pmNonLinearityLookup", true, false},
+                              {test02, 000, "pmNonLinearityPolynomial(): error/warning conditions", true, false},
+                              {test03, 000, "pmNonLinearityLookup(): error/warning conditions", true, false},
+                              {NULL}
+                          };
+
+#define NUM_ROWS 8
+#define NUM_COLS 8
+#define LOOKUP_FILENAME ".tmp_tst_pmNonLinearLookupFile"
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    //
+    // We generate a lookup file for future tests.  We should probably remove
+    // it when we're done.
+    //
+    FILE *fp = fopen(LOOKUP_FILENAME, "w");
+    ;
+    for (psS32 i=0;i<PS_MAX(NUM_COLS, NUM_ROWS)*3;i++) {
+        fprintf(fp, "%f %f\n", (float) i, (float) (2 * i));
+    }
+    fclose(fp);
+
+    //    system("rm LOOKUP_FILENAME");
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+int doNonLinearityPolynomialTest(int numCols, int numRows)
+{
+    int i;
+    int j;
+    float actual;
+    float expect;
+    int testStatus = true;
+    psImage *myImage = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    myReadout->image = myImage;
+    psPolynomial1D *myPoly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    myPoly->coeff[1] = 1.0;
+
+    printPositiveTestHeader(stdout, "pmNonLinear", "doNonLinearityPolynomialTest");
+
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+
+    myReadout = pmNonLinearityPolynomial(myReadout, myPoly);
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            expect = psPolynomial1DEval(myPoly, (float) (i + j));
+            actual = myReadout->image->data.F32[i][j];
+            if (FLT_EPSILON < fabs(expect - actual)) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                testStatus = false;
+            }
+        }
+    }
+
+
+    psFree(myReadout);
+    psFree(myPoly);
+    printFooter(stdout, "pmNonLinear", "doNonLinearityPolynomialTest", true);
+    return(testStatus);
+}
+
+int test00( void )
+{
+    int testStatus = 0;
+
+    testStatus |= doNonLinearityPolynomialTest(1, 1);
+    testStatus |= doNonLinearityPolynomialTest(NUM_COLS, 1);
+    testStatus |= doNonLinearityPolynomialTest(1, NUM_ROWS);
+    testStatus |= doNonLinearityPolynomialTest(NUM_COLS, NUM_ROWS);
+
+    return(testStatus);
+}
+
+int doNonLinearityLookupTest(int numCols, int numRows)
+{
+    int i;
+    int j;
+    float actual;
+    float expect;
+    int testStatus = true;
+    psImage *myImage = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    myReadout->image = myImage;
+
+    printPositiveTestHeader(stdout, "pmNonLinear", "doNonLinearityLookupTest");
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+
+    myReadout = pmNonLinearityLookup(myReadout, LOOKUP_FILENAME);
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            expect = (float) (2 * (i + j));
+            actual = myReadout->image->data.F32[i][j];
+            if (FLT_EPSILON < fabs(expect - actual)) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                testStatus = false;
+            }
+        }
+    }
+
+    psFree(myReadout);
+    printFooter(stdout, "pmNonLinear", "doNonLinearityLookupTest", true);
+    return(testStatus);
+}
+
+int test01( void )
+{
+    int testStatus = 0;
+
+    testStatus |= doNonLinearityLookupTest(1, 1);
+    testStatus |= doNonLinearityLookupTest(NUM_COLS, 1);
+    testStatus |= doNonLinearityLookupTest(1, NUM_ROWS);
+    testStatus |= doNonLinearityLookupTest (NUM_COLS, NUM_ROWS);
+
+    return(testStatus);
+}
+
+int test02( void )
+{
+    int i;
+    int j;
+    int testStatus = true;
+    psImage *myImage = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    pmReadout *rc = NULL;
+    myReadout->image = myImage;
+    psPolynomial1D *myPoly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, 2);
+    myPoly->coeff[1] = 1.0;
+
+    printPositiveTestHeader(stdout, "pmNonLinear", "Testing bad input parameter conditions.");
+    for (i=0;i<NUM_ROWS;i++) {
+        for (j=0;j<NUM_COLS;j++) {
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+
+    printf("------------------------------------------------------------\n");
+    printf("Calling pmNonLinearityPolynomial() with NULL input readout.  Should generate error, return NULL.\n");
+    rc = pmNonLinearityPolynomial(NULL, myPoly);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmNonLinearityPolynomial() returned a non-NULL pmReadout\n");
+        testStatus = false;
+    }
+
+    printf("------------------------------------------------------------\n");
+    printf("Calling pmNonLinearityPolynomial() with NULL input readout->image.  Should generate error, return NULL.\n");
+    psImage *tmpImage = myReadout->image;
+    myReadout->image = NULL;
+    rc = pmNonLinearityPolynomial(myReadout, myPoly);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmNonLinearityPolynomial() returned a non-NULL pmReadout\n");
+        testStatus = false;
+    }
+    myReadout->image = tmpImage;
+
+    printf("------------------------------------------------------------\n");
+    printf("Calling pmNonLinearityPolynomial() with NULL polynomial.  Should generate error, return NULL.\n");
+    rc = pmNonLinearityPolynomial(myReadout, NULL);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmNonLinearityPolynomial() returned a non-NULL pmReadout\n");
+        testStatus = false;
+    }
+
+    psFree(myReadout);
+    psFree(myPoly);
+    return(testStatus);
+}
+
+
+int test03Init(pmReadout *myReadout)
+{
+    for (psS32 i=0;i<NUM_ROWS;i++) {
+        for (psS32 j=0;j<NUM_COLS;j++) {
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+    return(0);
+}
+
+int test03()
+{
+    int testStatus = true;
+    psImage *myImage = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    pmReadout *rc = NULL;
+    myReadout->image = myImage;
+
+    test03Init(myReadout);
+    printf("------------------------------------------------------------\n");
+    printf("Calling pmNonLinearityLookup() with NULL input pmReadout.  Should generate error, return NULL.\n");
+    rc = pmNonLinearityLookup(NULL, LOOKUP_FILENAME);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmNonLinearityPolynomial() returned a non-NULL pmReadout\n");
+        testStatus = false;
+    }
+
+    printf("------------------------------------------------------------\n");
+    printf("Calling pmNonLinearityLookup() with NULL input pmReadout->image.  Should generate error, return NULL.\n");
+    psImage *tmpImage = myReadout->image;
+    myReadout->image = NULL;
+    rc = pmNonLinearityLookup(myReadout, LOOKUP_FILENAME);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmNonLinearityPolynomial() returned a non-NULL pmReadout\n");
+        testStatus = false;
+    }
+    myReadout->image = tmpImage;
+
+    printf("------------------------------------------------------------\n");
+    printf("Calling pmNonLinearityLookup() with non-existent lookup file.\n");
+    rc = pmNonLinearityLookup(myReadout, "I_DONT_EXIST");
+    if (rc == NULL) {
+        printf("TEST ERROR: pmNonLinearityPolynomial() returned a NULL pmReadout\n");
+        testStatus = false;
+    }
+
+
+    printf("------------------------------------------------------------\n");
+    printf("Calling pmNonLinearityLookup() with one pixels outside inFlux range.  Should generate warnings.\n");
+
+    psFree(myReadout);
+
+    printFooter(stdout, "pmNonLinear", "Testing bad input parameter conditions.", true);
+    return(testStatus);
+}
Index: /tags/RHL_v1_1/psModules/test/detrend/tst_pmSubtractBias.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/detrend/tst_pmSubtractBias.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/detrend/tst_pmSubtractBias.c	(revision 9533)
@@ -0,0 +1,1129 @@
+/** @file tst_pmSubtractBias.c
+ *
+ *  @brief Contains the tests for pmSubtractBias.c:
+ *
+ * test00a: This code will subtract full bias frames from the input image.
+ * XXX: Must test:
+ *  Various image offsets.
+ *  Various image size combinations.
+ *  Various data types for the bias and input images.
+ *  Ensure code works when CELL.TRIMSEC is not set.
+ * test00b: This code will subtract full dark frames from the input image.
+ * XXX: Must test:
+ *  Various image offsets.
+ *  Various image size combinations.
+ *  Various data types for the bias and input images.
+ *  Code properly determines CELL.DARKTIME from cell metadata.
+ *  Ensure code works when CELL.DARKTIME is not set.
+ *  Ensure code works when CELL.TRIMSEC is not set.
+ *  test03: Calculate a row overscan vector and subtract it from each
+ *  row in the input image.
+ * test05:
+ *
+ *  @author GLG, MHPCC
+ *
+ *  XXX: Memory leaks are not being detected.
+ *
+ *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-05-25 22:02:23 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#include "psTest.h"
+#include "pslib.h"
+#include "pmSubtractBias.h"
+static int test00a(void);
+static int test00b(void);
+//static int test01(void);
+//static int test02(void);
+//static int test03(void);
+//static int test04(void);
+static int test05(void);
+testDescription tests[] = {
+                              {test00a, 000, "doSubtractBiasFullFrame", 0, true},
+                              {test00b, 000, "doSubtractDarkFullFrame", 0, true},
+                              //                              {test01, 000, "pmSubtractBias", 0, true},
+                              //                              {test02, 000, "pmSubtractBias", 0, true},
+                              //                              {test03, 000, "pmSubtractBias", 0, true},
+                              //                              {test04, 000, "pmSubtractBias", 0, true},
+                              {test05, 000, "pmSubtractBias", 0, false},
+                              {NULL}
+                          };
+
+psS32 currentId = 0;
+psS32 memLeaks = 0;             // XXX: remove
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+
+    psTraceSetLevel(".", 0);
+    psTraceSetLevel("spline1DFree", 0);
+    psTraceSetLevel("calculateSecondDerivs", 0);
+    psTraceSetLevel("vectorBinDisectF32", 0);
+    psTraceSetLevel("vectorBinDisectF64", 0);
+    psTraceSetLevel("p_psVectorBinDisect", 0);
+    psTraceSetLevel("psSpline1DAlloc", 0);
+    psTraceSetLevel("psVectorFitSpline1D", 0);
+    psTraceSetLevel("psSpline1DEval", 0);
+    psTraceSetLevel("psSpline1DEvalVector", 0);
+
+    psS32 currentId = psMemGetId(); // XXX: remove
+    psS32 memLeaks = 0;             // XXX: remove
+    if (0) {
+        PRINT_MEMLEAKS(0);
+    }
+
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+#define NUM_ROWS 8
+#define NUM_COLS 8
+#define MAX_HEADER_MSG_LENGTH 1000
+#define POLYNOMIAL_FIT_ORDER 2
+#define NUM_OVERSCANS 2
+/******************************************************************************
+doSubtractBiasFullFrame(): a sample pmReadout as well as a bias image are
+created and the bias image is subtracted from the pmReadout.
+ *****************************************************************************/
+int doSubtractBiasFullFrame(int numCols, int numRows)
+{
+    int i;
+    int j;
+    float actual;
+    float expect;
+    int testStatus = 0;
+    psImage *tmpImage1 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *tmpImage2 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    pmReadout *myBias = pmReadoutAlloc(NULL);
+    myReadout->image = tmpImage1;
+    myBias->image = tmpImage2;
+
+    char *HeaderMessageStr = (char *) psAlloc(MAX_HEADER_MSG_LENGTH);
+    sprintf(HeaderMessageStr, "doSubtractBiasFullFrame(%d, %d)", numRows, numCols);
+    printPositiveTestHeader(stdout, "pmSubtractBias", HeaderMessageStr);
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+            myBias->image->data.F32[i][j] = 1.0;
+        }
+    }
+
+    myReadout = pmSubtractBias(myReadout, NULL, PM_FIT_NONE, false,
+                               NULL, 0, myBias, NULL);
+
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            expect = ((float) (i + j)) - 1.0;
+            actual = myReadout->image->data.F32[i][j];
+            if (FLT_EPSILON < fabs(expect - actual)) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                testStatus = 1;
+            }
+        }
+    }
+
+
+    psFree(myReadout);
+    psFree(myBias);
+    printFooter(stdout, "pmSubtractBias", HeaderMessageStr, true);
+    psFree(HeaderMessageStr);
+    return(testStatus);
+}
+
+
+int test00a( void )
+{
+    int testStatus = 0;
+
+    testStatus |= doSubtractBiasFullFrame(1, 1);
+    testStatus |= doSubtractBiasFullFrame(NUM_COLS, 1);
+    testStatus |= doSubtractBiasFullFrame(1, NUM_ROWS);
+    testStatus |= doSubtractBiasFullFrame(NUM_COLS, NUM_ROWS);
+    return(testStatus);
+}
+
+
+/******************************************************************************
+doSubtractDarkFullFrame(): a sample pmReadout as well as a dark image are
+created and the dark image is subtracted from the pmReadout.
+ *****************************************************************************/
+int doSubtractDarkFullFrame(int numCols, int numRows)
+{
+    int i;
+    int j;
+    float actual;
+    float expect;
+    int testStatus = 0;
+    psImage *tmpImage1 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *tmpImage2 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    pmReadout *myDark = pmReadoutAlloc(NULL);
+    myReadout->image = tmpImage1;
+    myDark->image = tmpImage2;
+
+    char *HeaderMessageStr = (char *) psAlloc(MAX_HEADER_MSG_LENGTH);
+    sprintf(HeaderMessageStr, "doSubtractDarkFullFrame(%d, %d)", numRows, numCols);
+    printPositiveTestHeader(stdout, "pmSubtractBias", HeaderMessageStr);
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+            myDark->image->data.F32[i][j] = 1.0;
+        }
+    }
+
+    myReadout = pmSubtractBias(myReadout, NULL, PM_FIT_NONE, false,
+                               NULL, 0, NULL, myDark);
+
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            expect = ((float) (i + j)) - 1.0;
+            actual = myReadout->image->data.F32[i][j];
+            if (FLT_EPSILON < fabs(expect - actual)) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                testStatus = 1;
+            }
+        }
+    }
+
+
+    psFree(myReadout);
+    psFree(myDark);
+    printFooter(stdout, "pmSubtractBias", HeaderMessageStr, true);
+    psFree(HeaderMessageStr);
+    return(testStatus);
+}
+
+
+int test00b( void )
+{
+    int testStatus = 0;
+
+    testStatus |= doSubtractDarkFullFrame(1, 1);
+    testStatus |= doSubtractDarkFullFrame(NUM_COLS, 1);
+    testStatus |= doSubtractDarkFullFrame(1, NUM_ROWS);
+    testStatus |= doSubtractDarkFullFrame(NUM_COLS, NUM_ROWS);
+    return(testStatus);
+}
+
+
+/*
+int doSubtractOverscansTestInputCases(int numCols, int numRows)
+{
+    int i;
+    int j;
+    int testStatus = 0;
+    psImage *tmpImage1 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *tmpImage2 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *tmpImage3 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *tmpImage4 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *tmpImage2Short = psImageAlloc(numCols-1, numRows-1, PS_TYPE_F32);
+    psImage *tmpImage3Short = psImageAlloc(numCols-1, numRows-1, PS_TYPE_F32);
+    psImage *tmpImage4Short = psImageAlloc(numCols-1, numRows-1, PS_TYPE_F32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    myReadout->image = tmpImage1;
+    pmReadout *rc = NULL;
+    psList *list;
+    psList *listShort;
+    psStats *stat = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    psImage *tmpImage5 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    pmReadout *myBias = pmReadoutAlloc(NULL);
+    myBias->image = tmpImage5;
+    printPositiveTestHeader(stdout, "pmSubtractBias", "Testing input parameter error conditions");
+ 
+    psImage *tmpImage5ShortRows = psImageAlloc(numCols, numRows-1, PS_TYPE_F32);
+    pmReadout *myBiasShortRows = pmReadoutAlloc(NULL);
+    myBiasShortRows->image = tmpImage5ShortRows;
+    psImage *tmpImage5ShortCols = psImageAlloc(numCols-1, numRows, PS_TYPE_F32);
+    pmReadout *myBiasShortCols = pmReadoutAlloc(NULL);
+    myBiasShortCols->image = tmpImage5ShortCols;
+ 
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+            tmpImage2->data.F32[i][j] = 3.0;
+            tmpImage3->data.F32[i][j] = 4.0;
+            tmpImage4->data.F32[i][j] = 5.0;
+            myBias->image->data.F32[i][j] = 1.0;
+        }
+    }
+    list = psListAlloc(tmpImage2);
+    psListAdd(list, PS_LIST_HEAD, tmpImage3);
+    psListAdd(list, PS_LIST_HEAD, tmpImage4);
+ 
+    for (i=0;i<numRows-1;i++) {
+        for (j=0;j<numCols-1;j++) {
+            tmpImage2Short->data.F32[i][j] = 3.0;
+            tmpImage3Short->data.F32[i][j] = 4.0;
+            tmpImage4Short->data.F32[i][j] = 5.0;
+        }
+    }
+    listShort = psListAlloc(tmpImage2Short);
+    psListAdd(listShort, PS_LIST_HEAD, tmpImage3Short);
+    psListAdd(listShort, PS_LIST_HEAD, tmpImage4Short);
+    for (i=0;i<numRows-1;i++) {
+        for (j=0;j<numCols;j++) {
+            myBiasShortRows->image->data.F32[i][j] = 1.0;
+        }
+    }
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols-1;j++) {
+            myBiasShortCols->image->data.F32[i][j] = 1.0;
+        }
+    }
+ 
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with NULL overscan list and PM_OVERSCAN_ALL.  Should generate error.\n");
+    rc = pmSubtractBias(myReadout, NULL, NULL, PM_OVERSCAN_ALL, stat, 0, PM_FIT_NONE, NULL);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractBias() did not return input pmReadout.\n");
+        testStatus = false;
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with NULL overscan list and PM_OVERSCAN_ROWS.  Should generate error.\n");
+    rc = pmSubtractBias(myReadout, NULL, NULL, PM_OVERSCAN_ROWS, stat, 0, PM_FIT_NONE, NULL);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractBias() did not return input pmReadout.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with NULL overscan list and PM_OVERSCAN_COLUMNS.  Should generate error.\n");
+    rc = pmSubtractBias(myReadout, NULL, NULL, PM_OVERSCAN_COLUMNS, stat, 0, PM_FIT_NONE, NULL);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractBias() did not return input pmReadout.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with non-NULL overscan list and PM_OVERSCAN_NONE.  Should generate warning.\n");
+    rc = pmSubtractBias(myReadout, NULL, list, PM_OVERSCAN_NONE, stat, 0, PM_FIT_NONE, myBias);
+ 
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            psF32 expect = ((float) (i + j)) - 1.0;
+            psF32 actual = rc->image->data.F32[i][j];
+            if (FLT_EPSILON < fabs(expect - actual)) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                testStatus = 1;
+            }
+ 
+            // Restore myReadout for next test.
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+ 
+    // XXX: This does not seem to be a requirement.
+    if (0) {
+        printf("------------------------------------------------------------------\n");
+        printf("Calling pmSubtractBias() with NULL overscan list and PM_OVERSCAN_NONE.  Should generate warning.\n");
+        rc = pmSubtractBias(myReadout, NULL, NULL, PM_OVERSCAN_NONE, stat,
+                            0, PM_FIT_NONE, myBias);
+ 
+        for (i=0;i<numRows;i++) {
+            for (j=0;j<numCols;j++) {
+                psF32 expect = ((float) (i + j)) - 1.0;
+                psF32 actual = rc->image->data.F32[i][j];
+                if (FLT_EPSILON < fabs(expect - actual)) {
+                    printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                    testStatus = 1;
+                }
+ 
+                // Restore myReadout for next test.
+                myReadout->image->data.F32[i][j] = (float) (i + j);
+            }
+        }
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with PM_OVERSCAN_NONE and PM_FIT_POLYNOMIAL.  Should generate Warning.\n");
+    rc = pmSubtractBias(myReadout, NULL, NULL, PM_OVERSCAN_NONE, stat, 0, PM_FIT_POLYNOMIAL, myBias);
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            psF32 expect = ((float) (i + j)) - 1.0;
+            psF32 actual = rc->image->data.F32[i][j];
+            if (FLT_EPSILON < fabs(expect - actual)) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                testStatus = 1;
+            }
+ 
+            // Restore myReadout for next test.
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+ 
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with PM_OVERSCAN_ALL and PM_FIT_SPLINE.  Should generate Warning.\n");
+    rc = pmSubtractBias(myReadout, NULL, NULL, PM_OVERSCAN_NONE, stat, 0, PM_FIT_SPLINE, myBias);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractBias() did not return input pmReadout.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+ 
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            psF32 expect = ((float) (i + j)) - 1.0;
+            psF32 actual = rc->image->data.F32[i][j];
+            if (FLT_EPSILON < fabs(expect - actual)) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                testStatus = 1;
+            }
+ 
+            // Restore myReadout for next test.
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+ 
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with multiple stats->options.  Should generate Warning.\n");
+    stat->options|= PS_STAT_SAMPLE_MEDIAN;
+    myReadout = pmSubtractBias(myReadout, NULL, list, PM_OVERSCAN_ALL, stat,
+                               0, PM_FIT_NONE, NULL);
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            psF32 expect = ((float) (i + j)) - 12.0;
+            psF32 actual = rc->image->data.F32[i][j];
+            if (FLT_EPSILON < fabs(expect - actual)) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                testStatus = 1;
+            }
+        }
+    }
+    stat->options = PS_STAT_SAMPLE_MEAN;
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() undersize overscans (PM_OVERSCAN_ROWS).  Should generate Warning.\n");
+    rc = pmSubtractBias(myReadout, NULL, listShort, PM_OVERSCAN_ROWS, stat,
+                        0, PM_FIT_NONE, NULL);
+    if (0) {
+        for (i=0;i<numRows;i++) {
+            for (j=0;j<numCols;j++) {
+                psF32 expect = ((float) (i + j)) - 12.0;
+                psF32 actual = rc->image->data.F32[i][j];
+                if (FLT_EPSILON < fabs(expect - actual)) {
+                    printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                    testStatus = 1;
+                }
+            }
+        }
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() undersize overscans (PM_OVERSCAN_COLUMNS).  Should generate Warning.\n");
+    rc = pmSubtractBias(myReadout, NULL, listShort, PM_OVERSCAN_COLUMNS, stat,
+                        0, PM_FIT_NONE, NULL);
+    if (0) {
+        for (i=0;i<numRows;i++) {
+            for (j=0;j<numCols;j++) {
+                psF32 expect = ((float) (i + j)) - 12.0;
+                psF32 actual = rc->image->data.F32[i][j];
+                if (FLT_EPSILON < fabs(expect - actual)) {
+                    printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                    testStatus = 1;
+                }
+            }
+        }
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() undersize bias image (short rows).  Should generate Error.\n");
+    myReadout = pmSubtractBias(myReadout, NULL, NULL, PM_OVERSCAN_NONE, NULL,
+                               0, PM_FIT_NONE, myBiasShortRows);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractBias() did not return input pmReadout.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+ 
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() undersize bias image (short columns).  Should generate Error.\n");
+    myReadout = pmSubtractBias(myReadout, NULL, NULL, PM_OVERSCAN_NONE, NULL,
+                               0, PM_FIT_NONE, myBiasShortCols);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractBias() did not return input pmReadout.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with bogus PM_FIT.  Should generate Error.\n");
+    myReadout = pmSubtractBias(myReadout, NULL, list, PM_OVERSCAN_ROWS, stat,
+                               0, 54321, NULL);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractBias() did not return input pmReadout.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    printf("Calling pmSubtractBias() with bogus overScanAxis.  Should generate Error.\n");
+    myReadout = pmSubtractBias(myReadout, NULL, list, 54321, stat,
+                               0, PM_FIT_NONE, NULL);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractBias() did not return input pmReadout.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+ 
+    if (0) {
+        for (i=0;i<numRows;i++) {
+            for (j=0;j<numCols;j++) {
+                psF32 expect = ((float) (i + j)) - 12.0;
+                psF32 actual = rc->image->data.F32[i][j];
+                if (FLT_EPSILON < fabs(expect - actual)) {
+                    printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                    testStatus = 1;
+                }
+            }
+        }
+    }
+ 
+    printf("------------------------------------------------------------------\n");
+    psFree(myReadout);
+    psFree(tmpImage2);
+    psFree(tmpImage3);
+    psFree(tmpImage4);
+    psFree(tmpImage2Short);
+    psFree(tmpImage3Short);
+    psFree(tmpImage4Short);
+    psFree(myBias);
+    psFree(myBiasShortRows);
+    psFree(myBiasShortCols);
+    psFree(stat);
+    psFree(list);
+    psFree(listShort);
+ 
+    printFooter(stdout, "pmSubtractBias", "Testing input parameter error conditions", true);
+    return(testStatus);
+}
+ 
+int test04( void )
+{
+    int testStatus = 0;
+ 
+    testStatus |= doSubtractOverscansTestInputCases(NUM_COLS, NUM_ROWS);
+    return(testStatus);
+}
+*/
+
+void PS_POLY1D_PRINT(
+    psPolynomial1D *poly)
+{
+    printf("-------------- PS_POLY1D_PRINT() --------------\n");
+    printf("poly->nX is %d\n", poly->nX);
+    for (psS32 i = 0 ; i < (1 + poly->nX) ; i++) {
+        printf("poly->coeff[%d] is %f\n", i, poly->coeff[i]);
+    }
+}
+
+void PS_PRINT_SPLINE2(psSpline1D *mySpline)
+{
+    printf("-------------- PS_PRINT_SPLINE2() --------------\n");
+    if (mySpline != NULL) {
+        printf("mySpline->n is %d\n", mySpline->n);
+        for (psS32 i = 0 ; i < mySpline->n ; i++) {
+            if (mySpline->spline[i] != NULL) {
+                PS_POLY1D_PRINT(mySpline->spline[i]);
+            }
+        }
+        if (mySpline->knots != NULL) {
+            PS_VECTOR_PRINT_F32(mySpline->knots);
+        }
+    } else {
+        printf("NULL\n");
+    }
+    printf("-------------- PS_PRINT_SPLINE2() DONE --------------\n");
+}
+
+
+
+
+
+
+/******************************************************************************
+doSubtractOverscansGeneric(): This is a general version of the
+bias subtraction tests which allows the various parameters to be specified
+as arguments.
+ *****************************************************************************/
+int doSubtractOverscansGeneric(
+    int imageNumCols,
+    int imageNumRows,
+    int overscanNumCols,
+    int overscanNumRows,
+    int numOverscans,
+    pmOverscanAxis overscanaxis,
+    pmFit fit,
+    psS32 nBin)
+{
+    int i;
+    int j;
+    float actual;
+    float expect;
+    int testStatus = 0;
+
+    printPositiveTestHeader(stdout, "pmSubtractBias", "PUT COMMENT HERE");
+    printf("---- doSubtractOverscansGeneric() ----\n");
+    printf("    Image size: %d by %d\n", imageNumRows, imageNumCols);
+    printf("    Overscan size: %d by %d\n", overscanNumRows, overscanNumCols);
+    printf("    Total Overscans: %d\n", numOverscans);
+    printf("    Binning factor: %d\n", nBin);
+    if (overscanaxis == PM_OVERSCAN_ROWS)
+        printf("    Overscan axis: PM_OVERSCAN_ROWS\n");
+    if (overscanaxis == PM_OVERSCAN_COLUMNS)
+        printf("    Overscan axis: PM_OVERSCAN_COLUMNS\n");
+    if (overscanaxis == PM_OVERSCAN_ALL)
+        printf("    Overscan axis: PM_OVERSCAN_ALL\n");
+    if (overscanaxis == PM_OVERSCAN_NONE)
+        printf("    Overscan axis: PM_OVERSCAN_NONE\n");
+    if (fit == PM_FIT_NONE)
+        printf("    Fit type: PM_FIT_NONE\n");
+    if (fit == PM_FIT_POLYNOMIAL)
+        printf("    Fit type: PM_FIT_POLYNOMIAL\n");
+    if (fit == PM_FIT_SPLINE)
+        printf("    Fit type: PM_FIT_SPLINE\n");
+
+    //
+    // Create and initialize input image, FPA hierarchy.
+    //
+    const psMetadata *camera = psMetadataAlloc();
+    pmFPA* fpa = pmFPAAlloc(camera);
+
+    if (fpa == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmFPAAlloc returned a NULL.\n");
+        return 1;
+    }
+
+    pmChip *chip = pmChipAlloc(fpa, "ChipName");
+    if (chip == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmChipAlloc returned a NULL.\n");
+        return 2;
+    }
+
+    pmCell *cell = pmCellAlloc(chip, (psMetadata *) camera, "CellName");
+    if (cell == NULL) {
+        psLogMsg(__func__,PS_LOG_ERROR, "TEST ERROR: pmCellAlloc returned a NULL.\n");
+        return 3;
+    }
+
+    pmReadout *myReadout = pmReadoutAlloc(cell);
+    myReadout->image = psImageAlloc(imageNumCols, imageNumRows, PS_TYPE_F32);
+    for (i=0;i<myReadout->image->numRows;i++) {
+        for (j=0;j<myReadout->image->numCols;j++) {
+            myReadout->image->data.F32[i][j] = (float) (i + j);
+        }
+    }
+
+    //
+    // Set overscan axis in the metadata.
+    //
+    psBool rc = false;
+    if (overscanaxis == PM_OVERSCAN_ROWS) {
+        rc = psMetadataAddS32(myReadout->parent->concepts, PS_LIST_HEAD, "CELL.READDIR", 0, NULL, 1);
+    } else if (overscanaxis == PM_OVERSCAN_COLUMNS) {
+        rc = psMetadataAddS32(myReadout->parent->concepts, PS_LIST_HEAD, "CELL.READDIR", 0, NULL, 2);
+    } else if (overscanaxis == PM_OVERSCAN_ALL) {
+        rc = psMetadataAddS32(myReadout->parent->concepts, PS_LIST_HEAD, "CELL.READDIR", 0, NULL, 3);
+    } else if (overscanaxis == PM_OVERSCAN_NONE) {
+        rc = psMetadataAddS32(myReadout->parent->concepts, PS_LIST_HEAD, "CELL.READDIR", 0, NULL, 0);
+    }
+    if (rc == false) {
+        printf("TEST ERROR: Could not set CELL.READDIR metadata.\n");
+        testStatus = 1;
+    }
+
+    psStats *stat = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    psPolynomial1D *myPoly = psPolynomial1DAlloc(PS_POLYNOMIAL_ORD, POLYNOMIAL_FIT_ORDER);
+    psSpline1D *mySpline = NULL;
+
+
+    if (0) {
+        if (overscanNumRows <= 0) {
+            overscanNumRows = 1;
+        }
+        if (overscanNumCols <= 0) {
+            overscanNumCols = 1;
+        }
+    }
+    psF32 oAverage = 0.0;
+    myReadout->bias = NULL;
+    for (psS32 i = 0 ; i < numOverscans ; i++) {
+        psImage *tmpImage = psImageAlloc(overscanNumCols, overscanNumRows, PS_TYPE_F32);
+        psF32 oValue = (float) (i + 3);
+        PS_IMAGE_SET_F32(tmpImage, oValue);
+        oAverage += oValue;
+        if (myReadout->bias == NULL) {
+            myReadout->bias = psListAlloc(tmpImage);
+        } else {
+            psListAdd(myReadout->bias, PS_LIST_HEAD, tmpImage);
+        }
+    }
+    oAverage/= (psF32) numOverscans;
+    if (0) {
+        if (fit == PM_FIT_NONE) {
+            myReadout = pmSubtractBias(myReadout, NULL, PM_FIT_NONE, overscanaxis,
+                                       stat, nBin, NULL, NULL);
+        } else if (fit == PM_FIT_POLYNOMIAL) {
+            myReadout = pmSubtractBias(myReadout, myPoly, PM_FIT_POLYNOMIAL, overscanaxis,
+                                       stat, nBin, NULL, NULL);
+        } else if (fit == PM_FIT_SPLINE) {
+            //        mySpline = psSpline1DAlloc();
+            myReadout = pmSubtractBias(myReadout, mySpline, PM_FIT_SPLINE, overscanaxis,
+                                       stat, nBin, NULL, NULL);
+        }
+        if (myReadout == NULL ) {
+            printf("TEST ERROR: pmSubtractBias() returned NULL.\n");
+            testStatus = 1;
+        } else {
+            for (i=0;i<imageNumRows;i++) {
+                for (j=0;j<imageNumCols;j++) {
+                    expect = ((float) (i + j)) - oAverage;
+                    actual = myReadout->image->data.F32[i][j];
+                    if (FLT_EPSILON < fabs(expect - actual)) {
+                        printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                        testStatus = 1;
+                    } else {
+                        //printf("GOOD: image[%d][%d] is %f, should be %f\n", i, j, actual, expect);
+                    }
+                }
+            }
+        }
+    }
+
+    // HEY
+    psFree(fpa);
+    psFree(stat);
+    psFree(myPoly);
+    psFree(mySpline);
+
+    printFooter(stdout, "pmSubtractBias", "Column Overscans", true);
+    return(testStatus);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/******************************************************************************
+test05a() The following combinations are tested here:
+ Overscan images are same size, no fit, bin factor is 1.
+ Overscan images are same size, no fit, bin factor is 2.
+ *****************************************************************************/
+int test05a(
+    psS32 imageNumCols,
+    psS32 imageNumRows,
+    psS32 overscanNumCols,
+    psS32 overscanNumRows)
+{
+    int testStatus = 0;
+
+    // imageNumCols, imageNumRows, overscanNumCols, overscanNumRows,
+    // overscanaxis, fit, nBin
+
+    //
+    // Overscan images are same size, no fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_NONE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_NONE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_NONE, 1);
+
+    //
+    // Overscan images are same size, no fit, bin factor is 2.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_NONE, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_NONE, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_NONE, 2);
+
+    return(testStatus);
+}
+
+
+/******************************************************************************
+test05b() The following combinations are tested here:
+ Overscan images are too small, spline fit, bin factor is 1.
+ Overscan images are too small, spline fit, bin factor is 2.
+ Overscan images are same size, spline fit, bin factor is 1.
+ Overscan images are same size, spline fit, bin factor is 2.
+ Overscan images are too big,   spline fit, bin factor is 1.
+ Overscan images are too big,   spline fit, bin factor is 2.
+ A single overscan image of the same size, spline fit, bin factor is 1.
+ 
+ Overscan images are too small, polynomial fit, bin factor is 1.
+ Overscan images are too small, polynomial fit, bin factor is 2.
+ Overscan images are same size, polynomial fit, bin factor is 1.
+ Overscan images are same size, polynomial fit, bin factor is 2.
+ Overscan images are too big,   polynomial fit, bin factor is 1.
+ Overscan images are too big,   polynomial fit, bin factor is 2.
+ A single overscan image of the same size, polynomial fit, bin factor is 1.
+ 
+XXX: Must add M-by-N image size tests.
+ *****************************************************************************/
+int test05b(
+    psS32 imageNumCols,
+    psS32 imageNumRows,
+    psS32 overscanNumCols,
+    psS32 overscanNumRows)
+{
+    int testStatus = 0;
+
+    //
+    // Overscan images are too small, spline fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_SPLINE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_SPLINE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_SPLINE, 1);
+
+    //
+    // Overscan images are too small, spline fit, bin factor is 2.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_SPLINE, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_SPLINE, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_SPLINE, 2);
+
+
+    //
+    // Overscan images are same size, spline fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_SPLINE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_SPLINE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_SPLINE, 1);
+
+    //
+    // Overscan images are same size, spline fit, bin factor is 2.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_SPLINE, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_SPLINE, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_SPLINE, 2);
+
+    //
+    // Overscan images are too big, spline fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_SPLINE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_SPLINE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_SPLINE, 1);
+
+    //
+    // Overscan images are too big, spline fit, bin factor is 2.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_SPLINE, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_SPLINE, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2 , NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_SPLINE, 2);
+
+
+    //
+    // A single overscan image of the same size, spline fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, 1,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_SPLINE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, 1,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_SPLINE, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, 1,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_SPLINE, 1);
+
+
+    //
+    // Overscan images are too small, polynomial fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    //
+    // Overscan images are too small, polynomial fit, bin factor is 2.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_POLYNOMIAL, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_POLYNOMIAL, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols-2, overscanNumRows-2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_POLYNOMIAL, 2);
+
+
+    //
+    // Overscan images are same size, polynomial fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    //
+    // Overscan images are same size, polynomial fit, bin factor is 2.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_POLYNOMIAL, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_POLYNOMIAL, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_POLYNOMIAL, 2);
+
+    //
+    // Overscan images are too big, polynomial fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    //
+    // Overscan images are too big, polynomial fit, bin factor is 2.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_POLYNOMIAL, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_POLYNOMIAL, 2);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols+2, overscanNumRows+2, NUM_OVERSCANS,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_POLYNOMIAL, 2);
+
+    //
+    // A single overscan image of the same size, polynomial fit, bin factor is 1.
+    //
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, 1,
+                  PM_OVERSCAN_ALL,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, 1,
+                  PM_OVERSCAN_COLUMNS,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    testStatus |= doSubtractOverscansGeneric(imageNumCols, imageNumRows,
+                  overscanNumCols, overscanNumRows, 1,
+                  PM_OVERSCAN_ROWS,
+                  PM_FIT_POLYNOMIAL, 1);
+
+    return(testStatus);
+}
+
+
+
+#define LOW_COLS 3
+#define LOW_ROWS 3
+
+/******************************************************************************
+test05(): See test05a() and test05b().
+ 
+We run the tests in test05b() starting with all possible combinations of
+sizes.
+ 
+XXX: Must add M-by-N image size tests.
+ *****************************************************************************/
+int test05()
+{
+    int testStatus = 0;
+    //    testStatus = test05a(NUM_COLS, NUM_ROWS, NUM_COLS, NUM_ROWS);
+
+    //    testStatus|= test05b(LOW_COLS, LOW_ROWS, LOW_COLS, LOW_ROWS);
+    //    testStatus|= test05b(LOW_COLS, LOW_ROWS, LOW_COLS, NUM_ROWS);
+    //    testStatus|= test05b(LOW_COLS, LOW_ROWS, NUM_COLS, LOW_ROWS);
+    //    testStatus|= test05b(LOW_COLS, LOW_ROWS, NUM_COLS, NUM_ROWS);
+
+    //    testStatus|= test05b(LOW_COLS, NUM_ROWS, LOW_COLS, LOW_ROWS);
+    //    testStatus|= test05b(LOW_COLS, NUM_ROWS, LOW_COLS, NUM_ROWS);
+    //    testStatus|= test05b(LOW_COLS, NUM_ROWS, NUM_COLS, LOW_ROWS);
+    //    testStatus|= test05b(LOW_COLS, NUM_ROWS, NUM_COLS, NUM_ROWS);
+
+    //    testStatus|= test05b(NUM_COLS, LOW_ROWS, LOW_COLS, LOW_ROWS);
+    //    testStatus|= test05b(NUM_COLS, LOW_ROWS, LOW_COLS, NUM_ROWS);
+    //    testStatus|= test05b(NUM_COLS, LOW_ROWS, NUM_COLS, LOW_ROWS);
+    //    testStatus|= test05b(NUM_COLS, LOW_ROWS, NUM_COLS, NUM_ROWS);
+
+    //    testStatus|= test05b(NUM_COLS, NUM_ROWS, LOW_COLS, LOW_ROWS);
+    //    testStatus|= test05b(NUM_COLS, NUM_ROWS, LOW_COLS, NUM_ROWS);
+    //    testStatus|= test05b(NUM_COLS, NUM_ROWS, NUM_COLS, LOW_ROWS);
+    testStatus|= test05b(NUM_COLS, NUM_ROWS, NUM_COLS, NUM_ROWS);
+
+
+    return(testStatus);
+}
+
Index: /tags/RHL_v1_1/psModules/test/detrend/tst_pmSubtractSky.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/detrend/tst_pmSubtractSky.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/detrend/tst_pmSubtractSky.c	(revision 9533)
@@ -0,0 +1,375 @@
+/** @file tst_pmSubtractSky.c
+ *
+ *  @brief Contains the tests for pmSubtractSky.c:
+ *
+ * test00: This code will test the pmSubtractSky routine.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-05-25 22:02:23 $
+ *
+ *  XXX: I added the CELL.TRIMSEC region code but there are not tests for it.
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include "psTest.h"
+#include "pslib.h"
+#include "pmSubtractSky.h"
+#define NUM_ROWS 512
+#define NUM_COLS 512
+#define POLY_X_ORDER 3
+#define POLY_Y_ORDER 3
+#define ERROR_TOLERANCE 1.0
+#define OBJECT_INTENSITY 2000.0
+static int test00(void);
+static int test01(void);
+testDescription tests[] = {
+                              {test00, 000, "pmSubtractSky", 0, false},
+                              {test01, 000, "pmSubtractSky: warning, error messages", 0, false},
+                              {NULL}
+                          };
+
+float func(int i, int j)
+{
+    return((float) (i + j));
+}
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+    //    test00();
+}
+
+/******************************************************************************
+ *****************************************************************************/
+int doSubtractSkySimple(int numCols, int numRows, int binFactor)
+{
+    int i;
+    int j;
+    int testStatus = 0;
+    psImage *tmpImageF32 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    //    pmReadout *myReadout = pmReadoutAlloc(numCols, numRows, tmpImageF32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    myReadout->image = tmpImageF32;
+    psStats *myStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    psPolynomial2D *myPoly = psPolynomial2DAlloc(PS_POLYNOMIAL_ORD, POLY_X_ORDER, POLY_Y_ORDER);
+
+    printPositiveTestHeader(stdout, "pmSubtractSky", "doSubtractSkySimple");
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            myReadout->image->data.F32[i][j] = func(i, j);
+        }
+    }
+
+    myReadout = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL,
+                              binFactor, myStats, 10.0);
+    if (myReadout == NULL) {
+        printf("TEST ERROR: pmSubtractSky() returned NULL.\n");
+        testStatus = 1;
+    } else {
+        for (i=0;i<numRows;i++) {
+            for (j=0;j<numCols;j++) {
+                if (ERROR_TOLERANCE < fabs(myReadout->image->data.F32[i][j])) {
+                    printf("TEST ERROR: image[%d][%d] is %f, should be 0.0\n", i, j, myReadout->image->data.F32[i][j]);
+                    testStatus = 1;
+                }
+            }
+        }
+    }
+    psFree(myReadout);
+    psFree(myStats);
+    psFree(myPoly);
+    printFooter(stdout, "pmSubtractSky", "doSubtractSkySimple", true);
+    return(testStatus);
+}
+
+/******************************************************************************
+ *****************************************************************************/
+int doSubtractSkyWithObjects(int numCols, int numRows, int binFactor)
+{
+    int i;
+    int j;
+    int testStatus = 0;
+    psImage *tmpImageF32 = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    //    pmReadout *myReadout = pmReadoutAlloc(numCols, numRows, tmpImageF32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    myReadout->image = tmpImageF32;
+    psStats *myStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    psPolynomial2D *myPoly = psPolynomial2DAlloc(PS_POLYNOMIAL_ORD, POLY_X_ORDER, POLY_Y_ORDER);
+    psImage *trueImage = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psF32 errorTolerance = ERROR_TOLERANCE * ((psF32) binFactor);
+
+    printPositiveTestHeader(stdout, "pmSubtractSky", "doSubtractSkyWithObjects");
+    for (i=0;i<numRows;i++) {
+        for (j=0;j<numCols;j++) {
+            myReadout->image->data.F32[i][j] = func(i, j);
+        }
+    }
+    // We insert a few bright spots in the image.
+    myReadout->image->data.F32[NUM_ROWS/4][NUM_COLS/4]+= OBJECT_INTENSITY;
+    myReadout->image->data.F32[NUM_ROWS/4][3*NUM_COLS/4]+= OBJECT_INTENSITY;
+    myReadout->image->data.F32[3*NUM_ROWS/4][NUM_COLS/4]+= OBJECT_INTENSITY;
+    myReadout->image->data.F32[3*NUM_ROWS/4][3*NUM_COLS/4]+= OBJECT_INTENSITY;
+    PS_IMAGE_SET_F32(trueImage, 0.0);
+    trueImage->data.F32[NUM_ROWS/4][NUM_COLS/4]+= OBJECT_INTENSITY;
+    trueImage->data.F32[NUM_ROWS/4][3*NUM_COLS/4]+= OBJECT_INTENSITY;
+    trueImage->data.F32[3*NUM_ROWS/4][NUM_COLS/4]+= OBJECT_INTENSITY;
+    trueImage->data.F32[3*NUM_ROWS/4][3*NUM_COLS/4]+= OBJECT_INTENSITY;
+
+    myReadout = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL,
+                              binFactor, myStats, 2.0);
+    if (myReadout == NULL) {
+        printf("TEST ERROR: pmSubtractSky() returned NULL.\n");
+        testStatus = 1;
+    } else {
+        for (i=0;i<numRows;i++) {
+            for (j=0;j<numCols;j++) {
+                if (errorTolerance < fabs(myReadout->image->data.F32[i][j] - trueImage->data.F32[i][j])) {
+                    printf("TEST ERROR: image[%d][%d] is %f, should be %f\n", i, j,
+                           myReadout->image->data.F32[i][j], trueImage->data.F32[i][j]);
+                    testStatus = 1;
+                }
+            }
+        }
+    }
+    psFree(myReadout);
+    psFree(myStats);
+    psFree(myPoly);
+    psFree(trueImage);
+    printFooter(stdout, "pmSubtractSky", "doSubtractSkyWithObjects", true);
+    return(testStatus);
+}
+
+
+int test00( void )
+{
+    int testStatus = 0;
+
+    psTraceSetLevel(".", 0);
+
+    // Bin Factor == 1
+    printf("doSubtractSkySimple(1, 1, 1)\n");
+    testStatus |= doSubtractSkySimple(1, 1, 1);
+    printf("doSubtractSkySimple(NUM_COLS, 1, 1)\n");
+    testStatus |= doSubtractSkySimple(NUM_COLS, 1, 1);
+
+    printf("doSubtractSkySimple(1, NUM_ROWS, 1)\n");
+    testStatus |= doSubtractSkySimple(1, NUM_ROWS, 1);
+    printf("doSubtractSkySimple(NUM_COLS, NUM_ROWS, 1)\n");
+    testStatus |= doSubtractSkySimple(NUM_COLS, NUM_ROWS, 1);
+
+    // Bin Factor == 2
+    printf("doSubtractSkySimple(1, 1, 2)\n");
+    testStatus |= doSubtractSkySimple(1, 1, 2);
+    printf("doSubtractSkySimple(NUM_COLS, 1, 2)\n");
+    testStatus |= doSubtractSkySimple(NUM_COLS, 1, 2);
+    printf("doSubtractSkySimple(1, NUM_ROWS, 2)\n");
+    testStatus |= doSubtractSkySimple(1, NUM_ROWS, 2);
+    printf("doSubtractSkySimple(NUM_COLS, NUM_ROWS, 2)\n");
+    testStatus |= doSubtractSkySimple(NUM_COLS, NUM_ROWS, 2);
+
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 1)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 1);
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 2)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 2);
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 4)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 8);
+
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 8)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 8);
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 16)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 16);
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 32)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 32);
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 64)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 64);
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 128)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 128);
+    printf("doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 256)\n");
+    testStatus |= doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 256);
+
+    return(testStatus);
+}
+
+#define NUM_ROWS_SMALL 16
+#define NUM_COLS_SMALL 16
+int test01( void )
+{
+    int testStatus = 0;
+    psS32 i;
+    psS32 j;
+    psImage *tmpImageF32 = psImageAlloc(NUM_COLS_SMALL, NUM_ROWS_SMALL, PS_TYPE_F32);
+    psImage *tmpImageF64 = psImageAlloc(NUM_COLS_SMALL, NUM_ROWS_SMALL, PS_TYPE_F64);
+    //    pmReadout *myReadout = pmReadoutAlloc(NUM_COLS_SMALL, NUM_ROWS_SMALL, tmpImageF32);
+    pmReadout *myReadout = pmReadoutAlloc(NULL);
+    myReadout->image = tmpImageF32;
+    pmReadout *rc = NULL;
+    psStats *myStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    psPolynomial2D *myPoly = psPolynomial2DAlloc(PS_POLYNOMIAL_ORD, POLY_X_ORDER, POLY_Y_ORDER);
+
+    printPositiveTestHeader(stdout, "pmSubtractSky", "Testing bad input parameter conditions.");
+    for (i=0;i<NUM_ROWS_SMALL;i++) {
+        for (j=0;j<NUM_COLS_SMALL;j++) {
+            myReadout->image->data.F32[i][j] = func(i, j);
+        }
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with NULL pmReadout.  Should error.\n\n");
+    rc = pmSubtractSky(NULL, (void *) myPoly, PM_FIT_POLYNOMIAL, 1, myStats, 2.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmSubtractSky() returned a non-NULL pmReadout\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with NULL pmReadout->image.  Should error.\n\n");
+    myReadout->image = NULL;
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL, 1, myStats, 2.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmSubtractSky() returned a non-NULL pmReadout\n");
+        testStatus = false;
+    }
+    myReadout->image = tmpImageF32;
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with PS_TYPE_F64 pmReadout->image.  Should error.\n\n");
+    myReadout->image = tmpImageF64;
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL, 1, myStats, 2.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmSubtractSky() returned a non-NULL pmReadout\n");
+        testStatus = false;
+    }
+    myReadout->image = tmpImageF32;
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with NULL fitSpec.  Should return image, no ERROR, no WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, NULL, PM_FIT_POLYNOMIAL, 1, myStats, 2.0);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractSky() returned something other than pmReadout\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with PM_FIT_NONE fit.  Should return image, no ERROR, no WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_NONE, 1, myStats, 2.0);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractSky() returned something other than pmReadout\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with PM_FIT_SPLINE fit.  Should return image, no ERROR, no WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_SPLINE, 1, myStats, 2.0);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractSky() returned something other than pmReadout\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with NULL myStats.  Should fit entire image, generate WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL, 1, NULL, 2.0);
+    for (i=0;i<NUM_ROWS_SMALL;i++) {
+        for (j=0;j<NUM_COLS_SMALL;j++) {
+            if (ERROR_TOLERANCE < fabs(rc->image->data.F32[i][j])) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be 0.0\n", i, j,
+                       rc->image->data.F32[i][j]);
+                testStatus = false;
+            }
+        }
+    }
+
+    printf("----------------------------------------------------------------\n");
+    psU64 oldOptions = myStats->options;
+    myStats->options = 0;
+    printf("Calling pmSubtractSky() with no myStats->options specified.  Should fit entire image, generate WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL, 1, myStats, 2.0);
+    for (i=0;i<NUM_ROWS_SMALL;i++) {
+        for (j=0;j<NUM_COLS_SMALL;j++) {
+            if (ERROR_TOLERANCE < fabs(rc->image->data.F32[i][j])) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be 0.0\n", i, j,
+                       rc->image->data.F32[i][j]);
+                testStatus = false;
+            }
+        }
+    }
+    myStats->options = oldOptions;
+
+    printf("----------------------------------------------------------------\n");
+    oldOptions = myStats->options;
+    myStats->options = PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_MEDIAN;
+    printf("Calling pmSubtractSky() with multiple myStats->options specified.  Should fit entire image, generate WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL, 1, myStats, 2.0);
+    for (i=0;i<NUM_ROWS_SMALL;i++) {
+        for (j=0;j<NUM_COLS_SMALL;j++) {
+            if (ERROR_TOLERANCE < fabs(rc->image->data.F32[i][j])) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be 0.0\n", i, j,
+                       rc->image->data.F32[i][j]);
+                testStatus = false;
+            }
+        }
+    }
+    myStats->options = oldOptions;
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with 0 binFactor.  Should fit entire image, generate WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL, 0, myStats, 2.0);
+    for (i=0;i<NUM_ROWS_SMALL;i++) {
+        for (j=0;j<NUM_COLS_SMALL;j++) {
+            if (ERROR_TOLERANCE < fabs(rc->image->data.F32[i][j])) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be 0.0\n", i, j,
+                       rc->image->data.F32[i][j]);
+                testStatus = false;
+            }
+        }
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with -1 binFactor.  Should fit entire image, generate WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL, -1, myStats, 2.0);
+    for (i=0;i<NUM_ROWS_SMALL;i++) {
+        for (j=0;j<NUM_COLS_SMALL;j++) {
+            if (ERROR_TOLERANCE < fabs(rc->image->data.F32[i][j])) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be 0.0\n", i, j,
+                       rc->image->data.F32[i][j]);
+                testStatus = false;
+            }
+        }
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with -1.0 clipSD.  Should fit entire image, generate WARNING.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL, 1, myStats, -1.0);
+    for (i=0;i<NUM_ROWS_SMALL;i++) {
+        for (j=0;j<NUM_COLS_SMALL;j++) {
+            if (ERROR_TOLERANCE < fabs(rc->image->data.F32[i][j])) {
+                printf("TEST ERROR: image[%d][%d] is %f, should be 0.0\n", i, j,
+                       rc->image->data.F32[i][j]);
+                testStatus = false;
+            }
+        }
+    }
+
+    printf("----------------------------------------------------------------\n");
+    printf("Calling pmSubtractSky() with bogus psFit.  Should generate Error.\n\n");
+    rc = pmSubtractSky(myReadout, (void *) myPoly, 54321, 1, myStats, -1.0);
+    if (rc != myReadout) {
+        printf("TEST ERROR: pmSubtractSky() returned something other than pmReadout\n");
+        testStatus = false;
+    }
+
+
+    //    myReadout = pmSubtractSky(myReadout, (void *) myPoly, PM_FIT_POLYNOMIAL,
+    //                              1, myStats, 2.0);
+
+
+    printf("----------------------------------------------------------------\n");
+    psFree(myReadout);
+    psFree(myStats);
+    psFree(myPoly);
+    psFree(tmpImageF64);
+    printFooter(stdout, "pmSubtractSky", "Testing bad input parameter conditions.", true);
+    return(testStatus);
+}
Index: /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmFlatField.stderr
===================================================================
--- /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmFlatField.stderr	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmFlatField.stderr	(revision 9533)
@@ -0,0 +1,69 @@
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmFlatField.c                                          *
+*            TestPoint: Test Point Driver{pmFlatField}                             *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|W|pmFlatField
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmFlatField
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmFlatField
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|E|pmFlatField (FILE:LINENO)
+    Unallowable operation: flat is NULL.
+<HOST>|E|pmFlatField (FILE:LINENO)
+    Unallowable operation: in->image is NULL.
+<HOST>|E|pmFlatField (FILE:LINENO)
+    Unallowable operation: flat->image is NULL.
+<HOST>|W|pmFlatField
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|E|pmFlatField (FILE:LINENO)
+    Input image size exceeds that of flat image: (3, 3) vs (2, 2)
+<HOST>|W|pmFlatField
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|E|pmFlatField (FILE:LINENO)
+    Input image mask size exceeds that of flat image: (5, 5) vs (3, 3)
+<HOST>|W|pmFlatField
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|E|pmFlatField (FILE:LINENO)
+    Total offset >= flat image size: (50, 50) vs (3, 3)
+<HOST>|W|pmFlatField
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|E|pmFlatField (FILE:LINENO)
+    Complex types not allowed for input image. Type: 2064
+<HOST>|W|pmFlatField
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|E|pmFlatField (FILE:LINENO)
+    Complex types not allowed for flat image. Type: 2064
+<HOST>|W|pmFlatField
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|E|pmFlatField (FILE:LINENO)
+    Input and flat image types differ: (1032 vs 1028)
+<HOST>|W|pmFlatField
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|E|pmFlatField (FILE:LINENO)
+    Mask must be PS_TYPE_MASK type. Type: 1028
+
+---> TESTPOINT PASSED (Test Point Driver{pmFlatField} | tst_pmFlatField.c)
+
Index: /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmFlatField.stdout
===================================================================
--- /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmFlatField.stdout	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmFlatField.stdout	(revision 9533)
@@ -0,0 +1,241 @@
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmFlatField.c                                          *
+*            TestPoint: pmFlatField{Test A - Divide input image by flat image}     *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+Input mask:
+0 0 0 
+0 0 0 
+0 0 0 
+
+Input image:
+6.000000 6.000000 6.000000 
+6.000000 6.000000 6.000000 
+6.000000 6.000000 6.000000 
+
+Flat image:
+2.000000 2.000000 2.000000 
+2.000000 2.000000 2.000000 
+2.000000 2.000000 2.000000 
+
+Resulting image:
+3.000000 3.000000 3.000000 
+3.000000 3.000000 3.000000 
+3.000000 3.000000 3.000000 
+
+
+---> TESTPOINT PASSED (pmFlatField{Test A - Divide input image by flat image} | tst_pmFlatField.c)
+
+
+
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmFlatField.c                                          *
+*            TestPoint: pmFlatField{Test B - Mask flat image data}                 *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+Input image:
+3.000000 3.000000 3.000000 
+3.000000 3.000000 3.000000 
+3.000000 3.000000 3.000000 
+
+Flat image:
+0.000000 0.000000 0.000000 
+0.000000 0.000000 0.000000 
+0.000000 0.000000 0.000000 
+
+Resulting mask:
+8 8 8 
+8 8 8 
+8 8 8 
+
+Resulting image:
+3.000000 3.000000 3.000000 
+3.000000 3.000000 3.000000 
+3.000000 3.000000 3.000000 
+
+
+---> TESTPOINT PASSED (pmFlatField{Test B - Mask flat image data} | tst_pmFlatField.c)
+
+
+
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmFlatField.c                                          *
+*            TestPoint: pmFlatField{Test C - Mask flat image data starting with non-null mask} *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+Input image:
+3.000000 3.000000 3.000000 
+3.000000 3.000000 3.000000 
+3.000000 3.000000 3.000000 
+
+Flat image:
+0.000000 0.000000 0.000000 
+0.000000 0.000000 0.000000 
+3.000000 -3.000000 0.000000 
+
+Flat image out:
+0.000000 0.000000 0.000000 
+0.000000 0.000000 0.000000 
+3.000000 0.000000 0.000000 
+
+Resulting mask:
+8 8 8 
+8 8 8 
+0 8 8 
+
+Resulting image:
+3.000000 3.000000 3.000000 
+3.000000 3.000000 3.000000 
+1.000000 3.000000 3.000000 
+
+
+---> TESTPOINT PASSED (pmFlatField{Test C - Mask flat image data starting with non-null mask} | tst_pmFlatField.c)
+
+
+
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmFlatField.c                                          *
+*            TestPoint: pmFlatField{Test D - Attempt to use null flat readout}     *
+*             TestType: Negative                                                   *
+*    ExpectedErrorText: Null not allowed for flat readout                          *
+*  ExpectedStatusValue: 0                                                          *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmFlatField{Test D - Attempt to use null flat readout} | tst_pmFlatField.c)
+
+
+
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmFlatField.c                                          *
+*            TestPoint: pmFlatField{Test E - Attempt to use null input image}      *
+*             TestType: Negative                                                   *
+*    ExpectedErrorText: Null not allowed for input image                           *
+*  ExpectedStatusValue: 0                                                          *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmFlatField{Test E - Attempt to use null input image} | tst_pmFlatField.c)
+
+
+
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmFlatField.c                                          *
+*            TestPoint: pmFlatField{Test F - Attempt tp use null flat image}       *
+*             TestType: Negative                                                   *
+*    ExpectedErrorText: Null not allowed for flat image                            *
+*  ExpectedStatusValue: 0                                                          *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmFlatField{Test F - Attempt tp use null flat image} | tst_pmFlatField.c)
+
+
+
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmFlatField.c                                          *
+*            TestPoint: pmFlatField{Test G - Attempt to use input image bigger than flat image} *
+*             TestType: Negative                                                   *
+*    ExpectedErrorText: Input image size exceeds that of flat image                *
+*  ExpectedStatusValue: 0                                                          *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmFlatField{Test G - Attempt to use input image bigger than flat image} | tst_pmFlatField.c)
+
+
+
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmFlatField.c                                          *
+*            TestPoint: pmFlatField{Test H - Attempt to use input image mask bigger than flat image} *
+*             TestType: Negative                                                   *
+*    ExpectedErrorText: Input image mask size exceeds that of flat image           *
+*  ExpectedStatusValue: 0                                                          *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmFlatField{Test H - Attempt to use input image mask bigger than flat image} | tst_pmFlatField.c)
+
+
+
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmFlatField.c                                          *
+*            TestPoint: pmFlatField{Test I - Attempt to use offset greater than input image} *
+*             TestType: Negative                                                   *
+*    ExpectedErrorText: Total offset >= input image size                           *
+*  ExpectedStatusValue: 0                                                          *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmFlatField{Test I - Attempt to use offset greater than input image} | tst_pmFlatField.c)
+
+
+
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmFlatField.c                                          *
+*            TestPoint: pmFlatField{Test J - Attempt to use complex input image}   *
+*             TestType: Negative                                                   *
+*    ExpectedErrorText: Complex types not allowed for input image                  *
+*  ExpectedStatusValue: 0                                                          *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmFlatField{Test J - Attempt to use complex input image} | tst_pmFlatField.c)
+
+
+
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmFlatField.c                                          *
+*            TestPoint: pmFlatField{Test K - Attempt to use complex flat image}    *
+*             TestType: Negative                                                   *
+*    ExpectedErrorText: Complex types not allowed for flat image                   *
+*  ExpectedStatusValue: 0                                                          *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmFlatField{Test K - Attempt to use complex flat image} | tst_pmFlatField.c)
+
+
+
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmFlatField.c                                          *
+*            TestPoint: pmFlatField{Test L - Attempt to use non-equal input and flat image types} *
+*             TestType: Negative                                                   *
+*    ExpectedErrorText: Input and flat image types differ                          *
+*  ExpectedStatusValue: 0                                                          *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmFlatField{Test L - Attempt to use non-equal input and flat image types} | tst_pmFlatField.c)
+
+
+
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmFlatField.c                                          *
+*            TestPoint: pmFlatField{Test M - Attempt to use non-mask type mask image} *
+*             TestType: Negative                                                   *
+*    ExpectedErrorText: Mask must be PS_TYPE_MASK type                             *
+*  ExpectedStatusValue: 0                                                          *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmFlatField{Test M - Attempt to use non-mask type mask image} | tst_pmFlatField.c)
+
+
+
+
Index: /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmMaskBadPixels.stderr
===================================================================
--- /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmMaskBadPixels.stderr	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmMaskBadPixels.stderr	(revision 9533)
@@ -0,0 +1,149 @@
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmMaskBadPixels.c                                      *
+*            TestPoint: Test Point Driver{pmMaskBadPixels - Create mask based on maskVal argument} *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|W|pmMaskBadPixels
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+
+---> TESTPOINT PASSED (Test Point Driver{pmMaskBadPixels - Create mask based on maskVal argument} | tst_pmMaskBadPixels.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmMaskBadPixels.c                                      *
+*            TestPoint: Test Point Driver{pmMaskBadPixels - Create mask based on saturation argument} *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|W|pmMaskBadPixels
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+
+---> TESTPOINT PASSED (Test Point Driver{pmMaskBadPixels - Create mask based on saturation argument} | tst_pmMaskBadPixels.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmMaskBadPixels.c                                      *
+*            TestPoint: Test Point Driver{pmMaskBadPixels - Create mask based on growVal and grow argum *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|W|pmMaskBadPixels
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+
+---> TESTPOINT PASSED (Test Point Driver{pmMaskBadPixels - Create mask based on growVal and grow arguments} | tst_pmMaskBadPixels.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmMaskBadPixels.c                                      *
+*            TestPoint: Test Point Driver{pmMaskBadPixels - Auto create mask based on maskVal argument} *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|W|pmMaskBadPixels
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+
+---> TESTPOINT PASSED (Test Point Driver{pmMaskBadPixels - Auto create mask based on maskVal argument} | tst_pmMaskBadPixels.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmMaskBadPixels.c                                      *
+*            TestPoint: Test Point Driver{pmMaskBadPixels - Attempt to use null mask} *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|W|pmMaskBadPixels
+    WARNING: in->parent is NULL.
+<HOST>|E|pmMaskBadPixels (FILE:LINENO)
+    Unallowable operation: mask is NULL.
+
+---> TESTPOINT PASSED (Test Point Driver{pmMaskBadPixels - Attempt to use null mask} | tst_pmMaskBadPixels.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmMaskBadPixels.c                                      *
+*            TestPoint: Test Point Driver{pmMaskBadPixels - Attempt tp use null input image} *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|E|pmMaskBadPixels (FILE:LINENO)
+    Unallowable operation: in->image is NULL.
+
+---> TESTPOINT PASSED (Test Point Driver{pmMaskBadPixels - Attempt tp use null input image} | tst_pmMaskBadPixels.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmMaskBadPixels.c                                      *
+*            TestPoint: Test Point Driver{pmMaskBadPixels - Attempt to use input image bigger than mask *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|W|pmMaskBadPixels
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|E|pmMaskBadPixels (FILE:LINENO)
+    Input image size exceeds that of mask image: (60, 60) vs (50, 50)
+
+---> TESTPOINT PASSED (Test Point Driver{pmMaskBadPixels - Attempt to use input image bigger than mask} | tst_pmMaskBadPixels.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmMaskBadPixels.c                                      *
+*            TestPoint: Test Point Driver{pmMaskBadPixels - Attempt to use input image mask bigger than *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|W|pmMaskBadPixels
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+
+---> TESTPOINT PASSED (Test Point Driver{pmMaskBadPixels - Attempt to use input image mask bigger than mask} | tst_pmMaskBadPixels.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmMaskBadPixels.c                                      *
+*            TestPoint: Test Point Driver{pmMaskBadPixels - Attempt to use offset greater than input im *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|W|pmMaskBadPixels
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|E|pmMaskBadPixels (FILE:LINENO)
+    Total offset >= mask image: (150, 150) vs (50, 50)
+
+---> TESTPOINT PASSED (Test Point Driver{pmMaskBadPixels - Attempt to use offset greater than input image} | tst_pmMaskBadPixels.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmMaskBadPixels.c                                      *
+*            TestPoint: Test Point Driver{pmMaskBadPixels - Attempt to use complex input image} *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|W|pmMaskBadPixels
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|E|pmMaskBadPixels (FILE:LINENO)
+    Complex types not allowed for input image. Type: 2064
+
+---> TESTPOINT PASSED (Test Point Driver{pmMaskBadPixels - Attempt to use complex input image} | tst_pmMaskBadPixels.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmMaskBadPixels.c                                      *
+*            TestPoint: Test Point Driver{pmMaskBadPixels - Attempt to use non-mask type mask image} *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|W|pmMaskBadPixels
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|E|pmMaskBadPixels (FILE:LINENO)
+    Mask must be PS_TYPE_MASK type. Type: 1032
+
+---> TESTPOINT PASSED (Test Point Driver{pmMaskBadPixels - Attempt to use non-mask type mask image} | tst_pmMaskBadPixels.c)
+
Index: /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmMaskBadPixels.stdout
===================================================================
--- /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmMaskBadPixels.stdout	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmMaskBadPixels.stdout	(revision 9533)
@@ -0,0 +1,416 @@
+Data mask:
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000001000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+
+Resulting mask:
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000001000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+
+Data mask:
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+
+Resulting mask:
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000001000000000000000000000000
+00000000000000000000000011100000000000000000000000
+00000000000000000000000111110000000000000000000000
+00000000000000000000000011100000000000000000000000
+00000000000000000000000001000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+
+Data mask:
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+
+Resulting mask:
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000001000000000000000000000000
+00000000000000000000011111111100000000000000000000
+00000000000000000001111111111111000000000000000000
+00000000000000000011111111111111100000000000000000
+00000000000000000111111111111111110000000000000000
+00000000000000000111111111111111110000000000000000
+00000000000000001111111111111111111000000000000000
+00000000000000001111111111111111111000000000000000
+00000000000000001111111111111111111000000000000000
+00000000000000001111111111111111111000000000000000
+00000000000000011111111111111111111100000000000000
+00000000000000001111111111111111111000000000000000
+00000000000000001111111111111111111000000000000000
+00000000000010001111111111111111111000100000000000
+00000000111111111111111111111111111111111110000000
+00000011111111111111111111111111111111111111100000
+00000111111111111111111111111111111111111111110000
+00001111111111111111111111111111111111111111111000
+00001111111111111111111111111111111111111111111000
+00011111111111111111111111111111111111111111111100
+00011111111111111111110001000111111111111111111100
+00011111111111111111110000000111111111111111111100
+00011111111111111111110000000111111111111111111100
+00111111111111111111111000001111111111111111111110
+00011111111111111111110000000111111111111111111100
+00011111111111111111110000000111111111111111111100
+00011111111111111111110000000111111111111111111100
+00011111111111111111110000000111111111111111111100
+00001111111111111111100000000011111111111111111000
+00001111111111111111100000000011111111111111111000
+00000111111111111111000000000001111111111111110000
+00000011111111111110000000000000111111111111100000
+00000000111111111000000000000000001111111110000000
+00000000000010000000000000000000000000100000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+
+Data mask:
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000001000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+
+Resulting mask:
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000001000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+00000000000000000000000000000000000000000000000000
+
Index: /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmNonLinear.stderr
===================================================================
--- /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmNonLinear.stderr	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmNonLinear.stderr	(revision 9533)
@@ -0,0 +1,86 @@
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmNonLinear.c                                          *
+*            TestPoint: Test Point Driver{pmNonLinearityPolynomial}                *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|W|pmNonLinearityPolynomial
+    WARNING: inputReadout->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmNonLinearityPolynomial
+    WARNING: inputReadout->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmNonLinearityPolynomial
+    WARNING: inputReadout->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmNonLinearityPolynomial
+    WARNING: inputReadout->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+
+---> TESTPOINT PASSED (Test Point Driver{pmNonLinearityPolynomial} | tst_pmNonLinear.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmNonLinear.c                                          *
+*            TestPoint: Test Point Driver{pmNonLinearityLookup}                    *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|W|pmNonLinearityLookup
+    WARNING: inputReadout->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmNonLinearityLookup
+    WARNING: inputReadout->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmNonLinearityLookup
+    WARNING: inputReadout->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmNonLinearityLookup
+    WARNING: inputReadout->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+
+---> TESTPOINT PASSED (Test Point Driver{pmNonLinearityLookup} | tst_pmNonLinear.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmNonLinear.c                                          *
+*            TestPoint: Test Point Driver{pmNonLinearityPolynomial(): error/warning conditions} *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|E|pmNonLinearityPolynomial (FILE:LINENO)
+    Unallowable operation: inputReadout is NULL.
+<HOST>|E|pmNonLinearityPolynomial (FILE:LINENO)
+    Unallowable operation: inputReadout->image is NULL.
+<HOST>|E|pmNonLinearityPolynomial (FILE:LINENO)
+    Unallowable operation: input1DPoly is NULL.
+
+---> TESTPOINT PASSED (Test Point Driver{pmNonLinearityPolynomial(): error/warning conditions} | tst_pmNonLinear.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmNonLinear.c                                          *
+*            TestPoint: Test Point Driver{pmNonLinearityLookup(): error/warning conditions} *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|E|pmNonLinearityLookup (FILE:LINENO)
+    Unallowable operation: inputReadout is NULL.
+<HOST>|E|pmNonLinearityLookup (FILE:LINENO)
+    Unallowable operation: inputReadout->image is NULL.
+<HOST>|W|pmNonLinearityLookup
+    WARNING: inputReadout->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|E|psVectorsReadFromFile (FILE:LINENO)
+    Failed to open file I_DONT_EXIST.
+<HOST>|W|pmNonLinearityLookup
+    WARNING: Lookup Table is too small.  Returning original pmReadout.
+
+---> TESTPOINT PASSED (Test Point Driver{pmNonLinearityLookup(): error/warning conditions} | tst_pmNonLinear.c)
+
Index: /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmNonLinear.stdout
===================================================================
--- /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmNonLinear.stdout	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/detrend/verified/tst_pmNonLinear.stdout	(revision 9533)
@@ -0,0 +1,95 @@
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmNonLinear.c                                          *
+*            TestPoint: pmNonLinear{doNonLinearityPolynomialTest}                  *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmNonLinear{doNonLinearityPolynomialTest} | tst_pmNonLinear.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmNonLinear.c                                          *
+*            TestPoint: pmNonLinear{doNonLinearityPolynomialTest}                  *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmNonLinear{doNonLinearityPolynomialTest} | tst_pmNonLinear.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmNonLinear.c                                          *
+*            TestPoint: pmNonLinear{doNonLinearityPolynomialTest}                  *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmNonLinear{doNonLinearityPolynomialTest} | tst_pmNonLinear.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmNonLinear.c                                          *
+*            TestPoint: pmNonLinear{doNonLinearityPolynomialTest}                  *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmNonLinear{doNonLinearityPolynomialTest} | tst_pmNonLinear.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmNonLinear.c                                          *
+*            TestPoint: pmNonLinear{doNonLinearityLookupTest}                      *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmNonLinear{doNonLinearityLookupTest} | tst_pmNonLinear.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmNonLinear.c                                          *
+*            TestPoint: pmNonLinear{doNonLinearityLookupTest}                      *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmNonLinear{doNonLinearityLookupTest} | tst_pmNonLinear.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmNonLinear.c                                          *
+*            TestPoint: pmNonLinear{doNonLinearityLookupTest}                      *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmNonLinear{doNonLinearityLookupTest} | tst_pmNonLinear.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmNonLinear.c                                          *
+*            TestPoint: pmNonLinear{doNonLinearityLookupTest}                      *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmNonLinear{doNonLinearityLookupTest} | tst_pmNonLinear.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmNonLinear.c                                          *
+*            TestPoint: pmNonLinear{Testing bad input parameter conditions.}       *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+------------------------------------------------------------
+Calling pmNonLinearityPolynomial() with NULL input readout.  Should generate error, return NULL.
+------------------------------------------------------------
+Calling pmNonLinearityPolynomial() with NULL input readout->image.  Should generate error, return NULL.
+------------------------------------------------------------
+Calling pmNonLinearityPolynomial() with NULL polynomial.  Should generate error, return NULL.
+------------------------------------------------------------
+Calling pmNonLinearityLookup() with NULL input pmReadout.  Should generate error, return NULL.
+------------------------------------------------------------
+Calling pmNonLinearityLookup() with NULL input pmReadout->image.  Should generate error, return NULL.
+------------------------------------------------------------
+Calling pmNonLinearityLookup() with non-existent lookup file.
+------------------------------------------------------------
+Calling pmNonLinearityLookup() with one pixels outside inFlux range.  Should generate warnings.
+
+---> TESTPOINT PASSED (pmNonLinear{Testing bad input parameter conditions.} | tst_pmNonLinear.c)
+
Index: /tags/RHL_v1_1/psModules/test/imcombine/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/imcombine/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/imcombine/.cvsignore	(revision 9533)
@@ -0,0 +1,7 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmImageCombine
+tst_pmReadoutCombine
Index: /tags/RHL_v1_1/psModules/test/imcombine/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/imcombine/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/imcombine/Makefile.am	(revision 9533)
@@ -0,0 +1,21 @@
+AM_LDFLAGS = -L$(top_builddir)/src -lpsmodules $(PSMODULES_LIBS)
+AM_CFLAGS  = @AM_CFLAGS@ $(PSMODULES_CFLAGS) $(SRCINC)
+
+TESTS = \
+    tst_pmImageCombine \
+    tst_pmReadoutCombine
+
+tst_pmImageCombine_SOURCES = tst_pmImageCombine.c
+tst_pmReadoutCombine_SOURCES = tst_pmReadoutCombine.c
+
+check_PROGRAMS = $(TESTS)
+
+TESTS_ENVIRONMENT = perl $(top_srcdir)/test/runTest --verified=$(srcdir)/verified
+
+tests: $(TESTS)
+
+EXTRA_DIST = verified
+
+CLEANFILES = $(TESTS) temp/* *~
+
+test: check
Index: /tags/RHL_v1_1/psModules/test/imcombine/tst_pmImageCombine.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/imcombine/tst_pmImageCombine.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/imcombine/tst_pmImageCombine.c	(revision 9533)
@@ -0,0 +1,365 @@
+/** @file tst_pmImageCombine.c
+ *
+ *  @brief Contains the tests for pmImageCombine.c:
+ *
+ *  test00: This code will test the various functions in the pmImageCombine.c file.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  XXX: Must verify the results internally.  Don't use stdout file.
+ *  XXX: Must test masks with pmRejectPixels()
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-03-04 01:01:34 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include "psTest.h"
+#include "pslib.h"
+#include "pmImageCombine.h"
+static int test00(void);
+testDescription tests[] = {
+                              {test00, 000, "pmCombineImages()", true, false},
+                              {NULL}
+                          };
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+psF32 genRanFloat(psF32 low,
+                  psF32 high)
+{
+    psF32 ran1 = (((psF32) (random() % 10000)) / 10000.0);
+    return(low + (ran1 * (high - low)));
+}
+
+psF32 genRanInt(psS32 low,
+                psS32 high)
+{
+    psF32 ran1 = (((psF32) (random() % 10000)) / 10000.0);
+    return((psS32) (low + (ran1 * (high - low))));
+}
+
+#define TST00_EXPANSION_FACTOR_X 1.0
+#define TST00_EXPANSION_FACTOR_Y 1.0
+#define TST00_OFFSET_X 0.0
+#define TST00_OFFSET_Y 0.0
+#define TST00_NUM_PIXELS 10
+#define TST00_MASK_VALUE 1
+#define TST00_NUM_ITERATIONS 4
+#define TST00_SIGMA_CLIP 1.0
+#define TST00_REJECTION_THRESHOLD 0.01
+#define TST00_GRADIENT_LIMIT 10.0
+/*******************************************************************************
+NOTE: This function returns FALSE if there were no errors.
+ ******************************************************************************/
+psBool testCombineImages(psS32 numRows,
+                         psS32 numCols,
+                         psS32 numImages)
+{
+    printf("Testing pmCombineImages(%d, %d, %d)\n", numRows, numCols, numImages);
+    bool testStatus = false;
+
+    psArray *images = psArrayAlloc(numImages);
+    psArray *errors = psArrayAlloc(numImages);
+    psArray *masks = psArrayAlloc(numImages);
+    images->n = images->nalloc;
+    errors->n = errors->nalloc;
+    masks->n = masks->nalloc;
+    for (psS32 i = 0 ; i < numImages ; i++) {
+        images->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        errors->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        masks->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_U8);
+        psImage *image = (psImage *) images->data[i];
+        psImage *error = (psImage *) errors->data[i];
+        psImage *mask = (psImage *) masks->data[i];
+        PS_IMAGE_SET_F32(image, 0.0);
+        PS_IMAGE_SET_F32(error, 1.0);
+        PS_IMAGE_SET_U8(mask, 0);
+
+        for (psS32 row = 0 ; row < numRows ; row++) {
+            for (psS32 col = 0 ; col < numCols ; col++) {
+                // Scale row/col to [-1.0:1.0]
+                psF32 rowScaled = ((psF32) (row - (numRows/2))) / ((psF32) (numRows/2));
+                psF32 colScaled = ((psF32) (col - (numCols/2))) / ((psF32) (numCols/2));
+                image->data.F32[row][col] = PS_SQR((2.0 - rowScaled) + (2.0 - colScaled)) + genRanFloat(0.0, 0.5);
+            }
+        }
+    }
+
+    //
+    // Same as above except the numImages is wrong
+    //
+    psArray *imagesLong = psArrayAlloc(numImages+1);
+    psArray *errorsLong = psArrayAlloc(numImages+1);
+    psArray *masksLong = psArrayAlloc(numImages+1);
+    imagesLong->n = imagesLong->nalloc;
+    errorsLong->n = errorsLong->nalloc;
+    masksLong->n = masksLong->nalloc;
+    for (psS32 i = 0 ; i < numImages+1 ; i++) {
+        imagesLong->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        errorsLong->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        masksLong->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_U8);
+        psImage *image = (psImage *) imagesLong->data[i];
+        psImage *error = (psImage *) errorsLong->data[i];
+        psImage *mask = (psImage *) masksLong->data[i];
+        PS_IMAGE_SET_F32(image, 0.0);
+        PS_IMAGE_SET_F32(error, 1.0);
+        PS_IMAGE_SET_U8(mask, 0);
+
+        for (psS32 row = 0 ; row < numRows ; row++) {
+            for (psS32 col = 0 ; col < numCols ; col++) {
+                // Scale row/col to [-1.0:1.0]
+                psF32 rowScaled = ((psF32) (row - (numRows/2))) / ((psF32) (numRows/2));
+                psF32 colScaled = ((psF32) (col - (numCols/2))) / ((psF32) (numCols/2));
+                image->data.F32[row][col] = PS_SQR((2.0 - rowScaled) + (2.0 - colScaled)) + genRanFloat(0.0, 0.5);
+            }
+        }
+    }
+
+    //
+    // Same as above except the type is wrong
+    //
+    psArray *imagesBadType = psArrayAlloc(numImages);
+    psArray *errorsBadType = psArrayAlloc(numImages);
+    psArray *masksBadType = psArrayAlloc(numImages);
+    imagesBadType->n = imagesBadType->nalloc;
+    errorsBadType->n = errorsBadType->nalloc;
+    masksBadType->n = masksBadType->nalloc;
+    for (psS32 i = 0 ; i < numImages ; i++) {
+        imagesBadType->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_F64);
+        errorsBadType->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_F64);
+        masksBadType->data[i] = (psPtr *) psImageAlloc(numCols, numRows, PS_TYPE_S8);
+        psImage *image = (psImage *) imagesBadType->data[i];
+        psImage *error = (psImage *) errorsBadType->data[i];
+        psImage *mask = (psImage *) masksBadType    ->data[i];
+        PS_IMAGE_SET_F32(image, 0.0);
+        PS_IMAGE_SET_F32(error, 1.0);
+        PS_IMAGE_SET_U8(mask, 0);
+
+        for (psS32 row = 0 ; row < numRows ; row++) {
+            for (psS32 col = 0 ; col < numCols ; col++) {
+                // Scale row/col to [-1.0:1.0]
+                psF32 rowScaled = ((psF32) (row - (numRows/2))) / ((psF32) (numRows/2));
+                psF32 colScaled = ((psF32) (col - (numCols/2))) / ((psF32) (numCols/2));
+                image->data.F32[row][col] = PS_SQR((2.0 - rowScaled) + (2.0 - colScaled)) + genRanFloat(0.0, 0.5);
+            }
+        }
+    }
+
+    psPixels *pixels = psPixelsAlloc(TST00_NUM_PIXELS);
+    pixels->n = pixels->nalloc;
+    for (psS32 p = 0 ; p < TST00_NUM_PIXELS ; p++) {
+        psS32 col =  genRanInt(0, numCols);
+        psS32 row =  genRanInt(0, numRows);
+        pixels->data[p].x = (psF32) col;
+        pixels->data[p].y = (psF32) row;
+        psS32 im = genRanInt(0, numImages-1);
+        psImage *image = (psImage *) images->data[im];
+        image->data.F32[row][col] += 100.0;
+        printf("Generating a bad pixel in image (%d) at (%d, %d)\n", im, row, col);
+    }
+
+    psArray *questionablePixels = NULL;
+    psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a NULL images.  Should generate error, return NULL.\n");
+    psImage *outImg = pmCombineImages(NULL, &questionablePixels, NULL, errors,
+                                      masks, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                                      TST00_SIGMA_CLIP, stats);
+    if (outImg != NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a non-NULL psImage.\n");
+        psFree(outImg);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a long images.  Should generate error, return NULL.\n");
+    outImg = pmCombineImages(NULL, &questionablePixels, imagesLong, errors,
+                             masks, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                             TST00_SIGMA_CLIP, stats);
+    if (outImg != NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a non-NULL psImage.\n");
+        psFree(outImg);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a bad type images.  Should generate error, return NULL.\n");
+    outImg = pmCombineImages(NULL, &questionablePixels, imagesBadType, errors,
+                             masks, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                             TST00_SIGMA_CLIP, stats);
+    if (outImg != NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a non-NULL psImage.\n");
+        psFree(outImg);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a long errors.  Should generate error, return NULL.\n");
+    outImg = pmCombineImages(NULL, &questionablePixels, images, errorsLong,
+                             masks, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                             TST00_SIGMA_CLIP, stats);
+    if (outImg != NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a non-NULL psImage.\n");
+        psFree(outImg);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a bad type errors.  Should generate error, return NULL.\n");
+    outImg = pmCombineImages(NULL, &questionablePixels, images, errorsBadType,
+                             masks, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                             TST00_SIGMA_CLIP, stats);
+    if (outImg != NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a non-NULL psImage.\n");
+        psFree(outImg);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a long masks.  Should generate error, return NULL.\n");
+    outImg = pmCombineImages(NULL, &questionablePixels, images, errors,
+                             masksLong, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                             TST00_SIGMA_CLIP, stats);
+    if (outImg != NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a non-NULL psImage.\n");
+        psFree(outImg);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a bad type masks.  Should generate error, return NULL.\n");
+    outImg = pmCombineImages(NULL, &questionablePixels, images, errors,
+                             masksBadType, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                             TST00_SIGMA_CLIP, stats);
+    if (outImg != NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a non-NULL psImage.\n");
+        psFree(outImg);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a NULL stats.  Should generate error, return NULL.\n");
+    outImg = pmCombineImages(NULL, &questionablePixels, images, errors,
+                             masks, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                             TST00_SIGMA_CLIP, NULL);
+    if (outImg != NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a non-NULL psImage.\n");
+        psFree(outImg);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with acceptable data.  Should generate a psImage.\n");
+    outImg = pmCombineImages(NULL, &questionablePixels, images, errors,
+                             masks, TST00_MASK_VALUE, pixels, TST00_NUM_ITERATIONS,
+                             TST00_SIGMA_CLIP, stats);
+    if (outImg == NULL) {
+        printf("TEST ERROR: pmCombineImages() returned a NULL psImage.\n");
+        testStatus = true;
+    }
+    if (0) {
+        for (psS32 p = 0 ; p < TST00_NUM_PIXELS ; p++) {
+            psS32 col = (psS32) (pixels->data[p]).x;
+            psS32 row = (psS32) (pixels->data[p]).y;
+            printf("------------------------------------------\n");
+            printf("Pixel (%d, %d) in combined image is %f\n", row, col, outImg->data.F32[row][col]);
+            for (psS32 i = 0 ; i < numImages ; i++) {
+                psImage *image = (psImage *) images->data[i];
+                printf("Pixel (after combine) (%d, %d) in image (%d) is %f\n", row, col, i, image->data.F32[row][col]);
+            }
+        }
+    }
+
+    if (questionablePixels->n != numImages) {
+        printf("TEST ERROR: pmCombineImages(): questionablePixels->n was %ld, should have been %d\n",
+               questionablePixels->n, numImages);
+        testStatus = true;
+    } else {
+        // XXX: We should internally verify this with the pixels list, not merely use the stdout.
+        for (psS32 i = 0 ; i < questionablePixels->n ; i++) {
+            psPixels *myPixels = (psPixels *) questionablePixels->data[i];
+            for (psS32 p = 0 ; p < myPixels->n ; p++) {
+                printf("Image %d, questionable pixel %d is (%f %f)\n",
+                       i, p, myPixels->data[p].y, myPixels->data[p].x);
+            }
+        }
+    }
+
+    psArray *expandTransforms = psArrayAlloc(numImages);
+    psArray *contractTransforms = psArrayAlloc(numImages);
+    expandTransforms->n = expandTransforms->nalloc;
+    contractTransforms->n = contractTransforms->nalloc;
+    for (psS32 im = 0 ; im < numImages ; im++) {
+        psPlaneTransform *ptExpand = psPlaneTransformAlloc(2, 2);
+        ptExpand->x->coeff[0][0] = TST00_OFFSET_X;
+        ptExpand->x->coeff[1][0] = TST00_EXPANSION_FACTOR_X;
+        ptExpand->y->coeff[0][0] = TST00_OFFSET_Y;
+        ptExpand->y->coeff[0][1] = TST00_EXPANSION_FACTOR_Y;
+        expandTransforms->data[im] = (psPtr *) ptExpand;
+
+        psPlaneTransform *ptContract = psPlaneTransformAlloc(2, 2);
+        ptContract->x->coeff[0][0] = -TST00_OFFSET_X;
+        ptContract->x->coeff[1][0] = 1.0 / TST00_EXPANSION_FACTOR_X;
+        ptContract->y->coeff[0][0] = -TST00_OFFSET_Y;
+        ptContract->y->coeff[0][1] = 1.0 / TST00_EXPANSION_FACTOR_Y;
+        contractTransforms->data[im] = (psPtr *) ptContract;
+    }
+
+    //-------------------------------------------------------------------------
+    //
+    // XXX: psRejectPixels() has known bugs.  Specifically, in the psImageTransform() call.
+    // We exclude this from our tests.
+    //
+    printf("\n\n\nCalling pmRejectPixels() with acceptable data.  Should generate a psArray.\n");
+    psArray *pixelRejects = pmRejectPixels(images, NULL, questionablePixels, expandTransforms,
+                                           contractTransforms, TST00_REJECTION_THRESHOLD,
+                                           TST00_GRADIENT_LIMIT);
+    if (pixelRejects == NULL) {
+        printf("TEST ERROR: pmRejectPixels() returned a NULL psArray.\n");
+        testStatus = true;
+    } else {
+        // XXX: We should internally verify this with the pixels list, not merely use the stdout.
+        for (psS32 i = 0 ; i < pixelRejects->n ; i++) {
+            psPixels *myPixels = (psPixels *) pixelRejects->data[i];
+            printf("tst_pmImageCombine.c: Image %d had %ld rejects.\n", i, myPixels->n);
+
+            for (psS32 p = 0 ; p < myPixels->n ; p++) {
+                printf("Image %d, rejected pixel %d is (%f %f)\n", i, p,
+                       myPixels->data[p].y, myPixels->data[p].x);
+            }
+        }
+        psFree(pixelRejects);
+    }
+
+    psFree(images);
+    psFree(errors);
+    psFree(masks);
+    psFree(imagesLong);
+    psFree(errorsLong);
+    psFree(masksLong);
+    psFree(imagesBadType);
+    psFree(errorsBadType);
+    psFree(masksBadType);
+    psFree(pixels);
+    psFree(stats);
+
+    return(testStatus);
+}
+
+/*******************************************************************************
+NOTE: This function returns TRUE if there were no errors.
+ ******************************************************************************/
+int test00( void )
+{
+    bool testStatus = false;
+
+    testStatus|= testCombineImages(10, 10, 5);
+
+    return(!testStatus);
+}
Index: /tags/RHL_v1_1/psModules/test/imcombine/tst_pmImageSubtract.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/imcombine/tst_pmImageSubtract.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/imcombine/tst_pmImageSubtract.c	(revision 9533)
@@ -0,0 +1,847 @@
+/** @file tst_pmImageSubtract.c
+ *
+ *  @brief Contains the tests for pmImageSubtract.c:
+ *
+ *  test00: This code will test the various functions in pmObjects.c
+ *
+ *  @author GLG, MHPCC
+ *
+ *  XXX: Most test simply ensure that the functions can be called with allowable
+ *  data.  More work need to be done to verify the results.
+ *
+ *  @version $Revision: 1.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-05-25 22:02:46 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include "psTest.h"
+#include "pslib.h"
+#include "pmImageSubtract.h"
+#define ERROR_TOLERANCE 1.0
+static int test00(void);
+static int test01(void);
+static int test02(void);
+static int test03(void);
+testDescription tests[] = {
+                              {test00, 000, "pmSubtractionKernelsAllocPOIS()", true, false},
+                              {test01, 000, "pmSubtractionKernelsAllocISIS()", true, false},
+                              {test02, 000, "pmSubtractionFindStamps()", true, false},
+                              {test03, 000, "pmSubtractionCalculateEquation()", true, false},
+                              {NULL}
+                          };
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+
+/*******************************************************************************
+NOTE: This function returns FALSE if there were no errors.
+ 
+XXX: Call with various unallowable input parameters.
+ 
+XXX: Untested: we don't loop through the (u, v, xOrder, yOrder) psVectors and
+ensure that each value is set correctly.
+ ******************************************************************************/
+psBool testPOISAlloc(psS32 size,
+                     psS32 SpatialOrder)
+{
+    printf("Testing pmSubtractionKernelsAllocPOIS(%d, %d)\n", size, SpatialOrder);
+
+    bool testStatus = false;
+    psS32 nBasisFunctions = (2 * size + 1) * (2 * size + 1) * (SpatialOrder + 1) * (SpatialOrder + 2) / 2;
+
+    psSubtractionKernels *kernels = pmSubtractionKernelsAllocPOIS(size, SpatialOrder);
+    if (kernels == NULL) {
+        printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() returned a NULL psSubtractionKernels.\n");
+        testStatus = true;
+    } else {
+        if (kernels->type != PM_SUBTRACTION_KERNEL_POIS) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated the wrong kernels->type.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->u == NULL) ||
+                (kernels->u->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a NULL ->u member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a incorrect length ->u member.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->v == NULL) ||
+                (kernels->v->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a NULL ->v member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a incorrect length ->v member.\n");
+            testStatus = true;
+        }
+
+        if (kernels->sigma != NULL) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a non-NULL ->sigma member.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->xOrder == NULL) ||
+                (kernels->xOrder->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a NULL ->xOrder member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a incorrect length ->xOrder member.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->yOrder == NULL) ||
+                (kernels->yOrder->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a NULL ->yOrder member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a incorrect length ->yOrder member.\n");
+            testStatus = true;
+        }
+
+        if (kernels->subIndex != 0) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a non-zero ->subIndex member (%d).\n", kernels->subIndex);
+            testStatus = true;
+        }
+
+        psS32 i = kernels->subIndex;
+        if ((kernels->u->data.F32[i] != 0) ||
+                (kernels->v->data.F32[i] != 0)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS(): the ->subIndex member points to a kernel with (%f, %f) (u, v) basis function.\n",
+                   kernels->u->data.F32[i], kernels->v->data.F32[i]);
+            testStatus = true;
+        }
+
+        if (kernels->preCalc != NULL) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated a non-NULL ->preCalc member.\n");
+            testStatus = true;
+        }
+
+        if (kernels->size != size) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated an incorrect ->size member (%d).\n", kernels->size);
+            testStatus = true;
+        }
+
+        if (kernels->spatialOrder != SpatialOrder) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocPOIS() generated an incorrect ->spatialOrder member (%d).\n", kernels->spatialOrder);
+            testStatus = true;
+        }
+    }
+    psFree(kernels);
+    return(testStatus);
+}
+
+/*******************************************************************************
+NOTE: This function returns TRUE if there were no errors.
+ ******************************************************************************/
+int test00( void )
+{
+    bool testStatus = false;
+
+    testStatus|= testPOISAlloc(1, 1);
+    testStatus|= testPOISAlloc(2, 3);
+    testStatus|= testPOISAlloc(3, 4);
+
+    return(!testStatus);
+}
+
+/*******************************************************************************
+NOTE: This function returns FALSE if there were no errors.
+ 
+XXX: Call with various unallowable input parameters.
+ 
+XXX: Untested: we don't loop through the (u, v, xOrder, yOrder) psVectors and
+ensure that each value is set correctly.  We don't ensure that he preCalc
+psImages are set correctly.
+ ******************************************************************************/
+psBool testISISAlloc(psS32 sigmaLength,
+                     psS32 orderLength,
+                     psS32 size,
+                     psS32 SpatialOrder)
+{
+    printf("Testing pmSubtractionKernelsAllocISIS(%d, %d, %d, %d)\n",
+           sigmaLength, orderLength, size, SpatialOrder);
+
+    psVector *sigmas = psVectorAlloc(sigmaLength, PS_TYPE_F32);
+    sigmas->n = sigmas->nalloc;
+    for (psS32 i = 0 ; i < sigmas->n ; i++) {
+        sigmas->data.F32[i] = 1.0 + (psF32) i;
+    }
+    psVector *orders = psVectorAlloc(orderLength, PS_TYPE_S32);
+    orders->n = orders->nalloc;
+    for (psS32 i = 0 ; i < orders->n ; i++) {
+        orders->data.S32[i] = i + 2;
+    }
+
+    bool testStatus = false;
+    psS32 numSigmas = sigmas->n;
+    psS32 nBasisFunctions = 0;
+    for (psS32 s = 0 ; s < numSigmas ; s++) {
+        for (psS32 o = 0 ; o < orders->n ; o++) {
+            nBasisFunctions+= ((orders->data.S32[o] + 1) * (orders->data.S32[o] + 2) / 2);
+        }
+    }
+    nBasisFunctions*= ((SpatialOrder + 1) * (SpatialOrder + 2) / 2);
+
+    psSubtractionKernels *kernels = pmSubtractionKernelsAllocISIS(sigmas, orders, size, SpatialOrder);
+    if (kernels == NULL) {
+        printf("TEST ERROR: pmSubtractionKernelsAllocISIS() returned a NULL psSubtractionKernels.\n");
+        testStatus = true;
+    } else {
+        if (kernels->type != PM_SUBTRACTION_KERNEL_ISIS) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated the wrong kernels->type.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->u == NULL) ||
+                (kernels->u->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a NULL ->u member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a incorrect length ->u member.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->v == NULL) ||
+                (kernels->v->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a NULL ->v member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a incorrect length ->v member.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->sigma == NULL) ||
+                (kernels->sigma->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a NULL ->sigma member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a incorrect length ->sigma member.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->xOrder == NULL) ||
+                (kernels->xOrder->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a NULL ->xOrder member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a incorrect length ->xOrder member.\n");
+            testStatus = true;
+        }
+
+        if ((kernels->yOrder == NULL) ||
+                (kernels->yOrder->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a NULL ->yOrder member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a incorrect length ->yOrder member.\n");
+            testStatus = true;
+        }
+
+        if (kernels->subIndex != 0) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a non-zero ->subIndex member (%d).\n", kernels->subIndex);
+            testStatus = true;
+        }
+
+        //
+        // Ensure that kernels->subIndex points to the correct kernel.
+        //
+        psS32 i = kernels->subIndex;
+        if ((kernels->u->data.F32[i] != 0.0) ||
+                (kernels->v->data.F32[i] != 0.0) ||
+                (kernels->xOrder->data.F32[i] != 0.0) ||
+                (kernels->yOrder->data.F32[i] != 0.0) ||
+                (kernels->sigma->data.F32[i] != sigmas->data.F32[0])) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS(): the ->subIndex member points to the wrong kernel.\n");
+            printf("TEST ERROR: (u, v, xOrder, yOrder, sigma) is (%f, %f, %f, %f, %f).\n",
+                   kernels->u->data.F32[i], kernels->v->data.F32[i],
+                   kernels->xOrder->data.F32[i], kernels->yOrder->data.F32[i],
+                   kernels->sigma->data.F32[i]);
+            testStatus = true;
+        }
+
+        //
+        // Ensure that the preCalc images are allocated correctly.
+        //
+        if ((kernels->preCalc == NULL) ||
+                (kernels->preCalc->n != nBasisFunctions)) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a NULL ->preCalc member or\n");
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated a incorrect length ->preCalc member.\n");
+            testStatus = true;
+        } else {
+            for (psS32 k = 0 ; k < kernels->u->n ; k++) {
+                psImage *kerImg = (psImage *) kernels->preCalc->data[k];
+                if (kerImg == NULL) {
+                    printf("TEST ERROR: the %d-th kernel preCalc image is NULL.\n", k);
+                    testStatus = true;
+                } else {
+                    if (kerImg->type.type != PS_TYPE_F32) {
+                        printf("TEST ERROR: preCalc image %d had ioncorrect type.\n", k);
+                        testStatus = true;
+                    }
+                    if ((kerImg->numRows != (1 + (2 * size))) ||
+                            (kerImg->numCols != (1 + (2 * size)))) {
+                        printf("TEST ERROR: preCalc image %d had incorrect size (%d, %d).\n", k,
+                               kerImg->numRows, kerImg->numCols);
+                        testStatus = true;
+                    }
+                }
+            }
+        }
+
+        if (kernels->size != size) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated an incorrect ->size member (%d).\n", kernels->size);
+            testStatus = true;
+        }
+
+        if (kernels->spatialOrder != SpatialOrder) {
+            printf("TEST ERROR: pmSubtractionKernelsAllocISIS() generated an incorrect ->spatialOrder member (%d).\n", kernels->spatialOrder);
+            testStatus = true;
+        }
+    }
+    psFree(sigmas);
+
+    psFree(kernels->u);
+    psFree(kernels->v);
+    psFree(kernels->sigma);
+    psFree(kernels->xOrder);
+    psFree(kernels->yOrder);
+    psFree(kernels->preCalc);
+    psFree(kernels);
+    psFree(orders);
+    return(testStatus);
+}
+
+/*******************************************************************************
+NOTE: This function returns TRUE if there were no errors.
+ ******************************************************************************/
+int test01( void )
+{
+    bool testStatus = false;
+
+    /*
+        testStatus|= testISISAlloc(1, 1, 1, 1);
+        testStatus|= testISISAlloc(2, 2, 2, 2);
+        testStatus|= testISISAlloc(2, 3, 4, 5);
+        testStatus|= testISISAlloc(3, 4, 5, 6);
+    */
+
+    return(!testStatus);
+}
+
+
+/*******************************************************************************
+NOTE: This function returns FALSE if there were no errors.
+ 
+XXX: Can we test to ensure that no stamps overlap?
+ 
+XXX: Test stamp alloc/dealloc functions.
+ ******************************************************************************/
+#define TST02_THRESHOLD 3.0
+#define TST02_MASK_VAL 1
+psBool testFindStamps(psS32 numCols,
+                      psS32 numRows,
+                      psS32 xNum,
+                      psS32 yNum,
+                      psS32 border)
+{
+    printf("Testing pmSubtractionFindStamps(%d, %d, %d, %d, %d)\n",
+           numCols, numRows, xNum, yNum, border);
+    bool testStatus = false;
+
+    // Create a test image and set a single pixel in the center of each stamp.
+    psImage *tstImg = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    int numStamps = 0;
+    PS_IMAGE_SET_F32(tstImg, 0.0);
+    for (psS32 j = 0; j < yNum; j++) {
+        for (psS32 i = 0; i < xNum; i++) {
+            psS32 yMin = border + j * (numRows - 2.0 * border) / yNum;
+            psS32 yMax = PS_MIN(numRows-1, (border + (j + 1) * (numRows - 2.0 * border) / yNum) - 1);
+            psS32 xMin = border + i * (numCols - 2.0 * border) / xNum;
+            psS32 xMax = PS_MIN(numCols-1, (border + (i + 1) * (numCols - 2.0 * border) / xNum) - 1);
+
+            tstImg->data.F32[(yMax+yMin)/2][(xMax+xMin)/2] = TST02_THRESHOLD + (psF32) (i + j);
+            numStamps++;
+        }
+    }
+    psImage *tmpMask= psImageAlloc(numCols, numRows, PS_TYPE_U8);
+    PS_IMAGE_SET_U8(tstImg, 0);
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a NULL psImage.  Should generate error, return NULL.\n");
+    psArray *stamps = pmSubtractionFindStamps(NULL, NULL, tmpMask, TST02_MASK_VAL,
+                      TST02_THRESHOLD, xNum, yNum,
+                      border);
+    if (stamps != NULL) {
+        printf("TEST ERROR: pmSubtractionFindStamps returned a non-NULL psArray.\n");
+        psFree(stamps);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a non-positive xNum.  Should generate error, return NULL.\n");
+    stamps = pmSubtractionFindStamps(NULL, tstImg, tmpMask, TST02_MASK_VAL,
+                                     TST02_THRESHOLD, 0, yNum,
+                                     border);
+    if (stamps != NULL) {
+        printf("TEST ERROR: pmSubtractionFindStamps returned a non-NULL psArray.\n");
+        psFree(stamps);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a non-positive yNum.  Should generate error, return NULL.\n");
+    stamps = pmSubtractionFindStamps(NULL, tstImg, tmpMask, TST02_MASK_VAL,
+                                     TST02_THRESHOLD, xNum, 0,
+                                     border);
+    if (stamps != NULL) {
+        printf("TEST ERROR: pmSubtractionFindStamps returned a non-NULL psArray.\n");
+        psFree(stamps);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with a non-positive border.  Should generate error, return NULL.\n");
+    stamps = pmSubtractionFindStamps(NULL, tstImg, tmpMask, TST02_MASK_VAL,
+                                     TST02_THRESHOLD, xNum, yNum,
+                                     0);
+    if (stamps != NULL) {
+        printf("TEST ERROR: pmSubtractionFindStamps returned a non-NULL psArray.\n");
+        psFree(stamps);
+        testStatus = true;
+    }
+
+    //-------------------------------------------------------------------------
+    printf("Calling with acceptable input parameters, non-NULL mask.\n");
+    stamps = pmSubtractionFindStamps(NULL, tstImg, tmpMask, TST02_MASK_VAL,
+                                     TST02_THRESHOLD, xNum, yNum,
+                                     border);
+    if (stamps == NULL) {
+        printf("TEST ERROR: pmSubtractionFindStamps returned a non-NULL psArray.\n");
+        testStatus = true;
+    } else {
+        if (stamps->n != numStamps) {
+            printf("TEST ERROR: %ld stamps were found, %d were expected.\n",
+                   stamps->n, numStamps);
+            testStatus = true;
+        }
+        for (psS32 s = 0 ; s < stamps->n ; s++) {
+            pmStamp *stamp = (pmStamp *) stamps->data[s];
+            psS32 row = stamp->y;
+            psS32 col = stamp->x;
+            // printf("Stamp %d at (%d, %d) has a value of %f\n", s, row, col, tstImg->data.F32[row][col]);
+            if (tstImg->data.F32[row][col] < TST02_THRESHOLD) {
+                if (stamp->status != PM_STAMP_NONE) {
+                    printf("TEST ERROR: stamp %d had peak value %f (below theshold) and the status was not set to PM_STAMP_NONE.\n",
+                           s, tstImg->data.F32[row][col]);
+                    testStatus = true;
+                }
+            } else {
+                if (stamp->status != PM_STAMP_RECALC) {
+                    printf("TEST ERROR: stamp %d had peak value %f (above theshold) and the status was not set to PM_STAMP_RECALC.\n",
+                           s, tstImg->data.F32[row][col]);
+                    testStatus = true;
+                }
+            }
+        }
+    }
+    psFree(stamps);
+
+    //-------------------------------------------------------------------------
+    printf("Calling with acceptable input parameters, NULL mask.\n");
+    stamps = pmSubtractionFindStamps(NULL, tstImg, NULL, TST02_MASK_VAL,
+                                     TST02_THRESHOLD, xNum, yNum,
+                                     border);
+    if (stamps == NULL) {
+        printf("TEST ERROR: pmSubtractionFindStamps returned a non-NULL psArray.\n");
+        testStatus = true;
+    } else {
+        if (stamps->n != numStamps) {
+            printf("TEST ERROR: %ld stamps were found, %d were expected.\n",
+                   stamps->n, numStamps);
+            testStatus = true;
+        }
+        for (psS32 s = 0 ; s < stamps->n ; s++) {
+            pmStamp *stamp = (pmStamp *) stamps->data[s];
+            psS32 row = stamp->y;
+            psS32 col = stamp->x;
+            //printf("Stamp %d at (%d, %d) has a value of %f\n", s, row, col, tstImg->data.F32[row][col]);
+            if (tstImg->data.F32[row][col] < TST02_THRESHOLD) {
+                if (stamp->status != PM_STAMP_NONE) {
+                    printf("TEST ERROR: stamp %d had peak value %f (below theshold) and the status was not set to PM_STAMP_NONE.\n",
+                           s, tstImg->data.F32[row][col]);
+                    testStatus = true;
+                }
+            } else {
+                if (stamp->status != PM_STAMP_RECALC) {
+                    printf("TEST ERROR: stamp %d had peak value %f (above theshold) and the status was not set to PM_STAMP_RECALC.\n",
+                           s, tstImg->data.F32[row][col]);
+                    testStatus = true;
+                }
+            }
+        }
+    }
+
+    psFree(tstImg);
+    psFree(tmpMask);
+    psFree(stamps);
+
+    return(testStatus);
+}
+
+
+
+/*******************************************************************************
+NOTE: This function returns TRUE if there were no errors.
+ ******************************************************************************/
+int test02( void )
+{
+    bool testStatus = false;
+
+    testStatus|= testFindStamps(100, 100, 2, 2, 2);
+    testStatus|= testFindStamps(100, 100, 10, 10, 2);
+
+    return(!testStatus);
+}
+
+
+psF32 genRanFloat(psF32 low,
+                  psF32 high)
+{
+    psF32 ran1 = (((psF32) (random() % 10000)) / 10000.0);
+    return(low + (ran1 * (high - low)));
+}
+
+//
+// XXX: POIS kernels are producing NANs if the image size is 20 or less.
+//
+// XXX: ISIS kernels are producing NANS if the TST03_ORDER_LENGTH is 2 or larger.
+//
+#define TST03_THRESHOLD  3.0
+#define TST03_MASK_VAL  1
+#define TST03_KERNEL_SIZE 2
+#define TST03_SPATIAL_ORDER 2
+#define TST03_ORDER_LENGTH 1
+#define TST03_SIGMA_LENGTH 1
+#define TST03_PSF_MAX  10.0
+#define TST03_BG 0.0
+#define TST03_IMAGE_SIZE 25
+#define TST03_NUM_COLS  TST03_IMAGE_SIZE
+#define TST03_NUM_ROWS  TST03_IMAGE_SIZE
+#define TST03_NUM_STAMPS 2
+#define TST03_NUM_STAMPS_COLS TST03_NUM_STAMPS
+#define TST03_NUM_STAMPS_ROWS TST03_NUM_STAMPS
+#define TST03_BORDER  TST03_KERNEL_SIZE
+//#define TST03_FOOTPRINT (((TST03_IMAGE_SIZE - (2 * TST03_BORDER)) / TST03_NUM_STAMPS) - TST03_KERNEL_SIZE)
+#define TST03_FOOTPRINT  4
+#define TST03_PSF_WIDTH  (TST03_FOOTPRINT/2 - 1)
+
+/*******************************************************************************
+This routine generates an object in the center of the stamp defined by the
+(xMin, xMax) and (yMin, yMax) boundary.
+ ******************************************************************************/
+psBool genObject(psImage *tstImg,
+                 psImage *refImg,
+                 psS32 xMin,
+                 psS32 xMax,
+                 psS32 yMin,
+                 psS32 yMax)
+{
+    if (((1 + 2 * TST03_PSF_WIDTH) > (xMax - xMin)) ||
+            ((1 + 2 * TST03_PSF_WIDTH) > (yMax - yMin))) {
+        printf("INCORRECT TEST CONFIGURATION: TST03_PSF_WIDTH is too big.\n");
+        printf("TST03_PSF_WIDTH is %d: (xMin - xMax) is %d\n", TST03_PSF_WIDTH, (xMax - xMin));
+        return(FALSE);
+    }
+    //
+    // This code basically creates a peak at the center of the stamp with
+    // a height of TST03_PSF_MAX
+    //
+    psS32 xCenter = (xMax + xMin) / 2;
+    psS32 yCenter = (yMax + yMin) / 2;
+    psF32 subImageWidth = 1.0 + sqrtf(PS_SQR(((psF32) ((yMax-yMin)/2))) + PS_SQR(((psF32) ((xMax-xMin)/2))));
+    for (psS32 y = yMin ; y <= yMax ; y++) {
+        for (psS32 x = xMin ; x <= xMax ; x++) {
+            psF32 dist = sqrtf(PS_SQR((psF32) (y - yCenter)) + PS_SQR((psF32) (x - xCenter)));
+            psF32 pixel = TST03_PSF_MAX * PS_SQR(((psF32) (subImageWidth - dist)) / ((psF32) subImageWidth));
+            if (pixel < 0.0) {
+                pixel = 0.0;
+            }
+            refImg->data.F32[y][x] = pixel + TST03_BG;
+            tstImg->data.F32[y][x] = pixel + TST03_BG;
+            // Add some noise for the test image.
+            tstImg->data.F32[y][x]+= genRanFloat(0.0, 1.0);
+        }
+    }
+
+    return(TRUE);
+}
+
+/*******************************************************************************
+NOTE: This function returns FALSE if there were no errors.
+ 
+XXX: We should use a larger variety of input parameter configurations.
+ 
+I test the following functions here (since they linearly rely on a set of data
+structures that the previoues ones generate):
+    pmSubtractionCalculateEquation()
+    pmSubtractionSolveEquation()
+    pmSubtractionRejectStamps()
+    pmSubtractionKernelImage()
+ ******************************************************************************/
+psBool testSubCalcEqu(psS32 numCols,
+                      psS32 numRows,
+                      psS32 xNum,
+                      psS32 yNum,
+                      psS32 border,
+                      pmSubtractionKernelsType KernelType)
+{
+    printf("Testing pmSubtractionCalculateEquation(): \n");
+    printf("    image size is (%d, %d)\n", numRows, numCols);
+    printf("    num stamps is (%d, %d).  Border is %d\n", yNum, xNum, border);
+    if (KernelType == PM_SUBTRACTION_KERNEL_POIS) {
+        printf("   kernel type is PM_SUBTRACTION_KERNEL_POIS.\n");
+    } else if (KernelType == PM_SUBTRACTION_KERNEL_ISIS) {
+        printf("   kernel type is PM_SUBTRACTION_KERNEL_ISIS.\n");
+    }
+    bool testStatus = false;
+
+    // Create a test image and set a single pixel in the center of each stamp.
+    psImage *tstImg = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *refImg = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psImage *maskImg = psImageAlloc(numCols, numRows, PS_TYPE_U8);
+    PS_IMAGE_SET_F32(tstImg, 0.0);
+    PS_IMAGE_SET_F32(maskImg, 0);
+    for (psS32 j = 0; j < yNum; j++) {
+        for (psS32 i = 0; i < xNum; i++) {
+            psS32 yMin = border + j * (numRows - 2.0 * border) / yNum;
+            psS32 yMax = PS_MIN(numRows-1, (border + (j + 1) * (numRows - 2.0 * border) / yNum) - 1);
+            psS32 xMin = border + i * (numCols - 2.0 * border) / xNum;
+            psS32 xMax = PS_MIN(numCols-1, (border + (i + 1) * (numCols - 2.0 * border) / xNum) - 1);
+
+            genObject(tstImg, refImg, xMin, xMax, yMin, yMax);
+        }
+    }
+    printf("Generating stamps...\n");
+    psArray *stamps = pmSubtractionFindStamps(NULL, tstImg, NULL, TST03_MASK_VAL,
+                      TST03_THRESHOLD, xNum, yNum,
+                      border);
+
+    //
+    // PsVectors sigmas and orders are for ISIS kernels only.
+    //
+    psVector *sigmas = psVectorAlloc(TST03_SIGMA_LENGTH, PS_TYPE_F32);
+    sigmas->n = sigmas->nalloc;
+    for (psS32 i = 0 ; i < sigmas->n ; i++) {
+        sigmas->data.F32[i] = 1.0 + (psF32) i;
+    }
+    psVector *orders = psVectorAlloc(TST03_ORDER_LENGTH, PS_TYPE_S32);
+    orders->n = orders->nalloc;
+    for (psS32 i = 0 ; i < orders->n ; i++) {
+        orders->data.S32[i] = i + 2;
+    }
+
+    //
+    // Create the subtraction kernels
+    //
+    printf("Generating kernel basis functions...\n");
+    psSubtractionKernels *myKernels = NULL;
+    if (KernelType == PM_SUBTRACTION_KERNEL_POIS) {
+        myKernels = pmSubtractionKernelsAllocPOIS(TST03_KERNEL_SIZE, TST03_SPATIAL_ORDER);
+    } else if (KernelType == PM_SUBTRACTION_KERNEL_ISIS) {
+        myKernels = pmSubtractionKernelsAllocISIS(sigmas, orders, TST03_KERNEL_SIZE, TST03_SPATIAL_ORDER);
+    }
+
+    if ((stamps == NULL) ||
+            (myKernels == NULL)) {
+        printf("TEST ERROR: stamps or myKernels is NULL.\n");
+        testStatus = true;
+    } else {
+        //-------------------------------------------------------------------------
+        printf("Calling with a NULL psArray stamps.  Should generate error, return FALSE.\n");
+        psBool rc = pmSubtractionCalculateEquation(NULL, refImg, tstImg, myKernels, TST03_FOOTPRINT);
+        if (rc == TRUE) {
+            printf("TEST ERROR: pmSubtractionCalculateEquation() returned TRUE.\n");
+            testStatus = true;
+        }
+
+        //-------------------------------------------------------------------------
+        printf("Calling with a NULL reference images.  Should generate error, return FALSE.\n");
+        rc = pmSubtractionCalculateEquation(stamps, NULL, tstImg, myKernels, TST03_FOOTPRINT);
+        if (rc == TRUE) {
+            printf("TEST ERROR: pmSubtractionCalculateEquation() returned TRUE.\n");
+            testStatus = true;
+        }
+
+        //-------------------------------------------------------------------------
+        printf("Calling with a NULL input images.  Should generate error, return FALSE.\n");
+        rc = pmSubtractionCalculateEquation(stamps, refImg, NULL, myKernels, TST03_FOOTPRINT);
+        if (rc == TRUE) {
+            printf("TEST ERROR: pmSubtractionCalculateEquation() returned TRUE.\n");
+            testStatus = true;
+        }
+
+        //-------------------------------------------------------------------------
+        printf("Calling with a NULL kernel basis functions.  Should generate error, return FALSE.\n");
+        rc = pmSubtractionCalculateEquation(stamps, refImg, tstImg, NULL, TST03_FOOTPRINT);
+        if (rc == TRUE) {
+            printf("TEST ERROR: pmSubtractionCalculateEquation() returned TRUE.\n");
+            testStatus = true;
+        }
+
+        //-------------------------------------------------------------------------
+        printf("Calling with acceptable input parameters.  Should return TRUE.\n");
+
+        rc = pmSubtractionCalculateEquation(stamps, refImg, tstImg, myKernels, TST03_FOOTPRINT);
+        if (rc != TRUE) {
+            printf("TEST ERROR: pmSubtractionCalculateEquation() returned FALSE.\n");
+            testStatus = true;
+        } else {
+
+            if (0) {
+                for (psS32 s = 0 ; s < stamps->n ; s++) {
+                    printf("********************************* Stamp %d *********************************\n", s);
+                    pmStamp *stamp = (pmStamp *) stamps->data[s];
+                    if (stamp->vector != NULL) {
+                        PS_VECTOR_PRINT_F64(stamp->vector);
+                    }
+                    if (stamp->matrix != NULL) {
+                        printf("Stamp matrix size is (%d, %d)\n", stamp->matrix->numRows, stamp->matrix->numCols);
+                        PS_IMAGE_PRINT_F64(stamp->matrix);
+                    }
+                }
+            }
+
+
+            //-------------------------------------------------------------------------
+            printf("Calling pmSubtractionSolveEquation() with a NULL stamp argument.  Should generate error, return FALSE.\n");
+            psVector *solution = pmSubtractionSolveEquation(NULL, NULL);
+            if (solution != NULL) {
+                printf("TEST ERROR: pmSubtractionSolveEquation() returned non-NULL.\n");
+                testStatus = true;
+            }
+
+            //-------------------------------------------------------------------------
+            printf("Calling pmSubtractionSolveEquation() with acceptable input parameters.  Should return non-NULL.\n");
+            solution = pmSubtractionSolveEquation(NULL, stamps);
+            if (solution == NULL) {
+                printf("TEST ERROR: pmSubtractionSolveEquation() returned NULL.\n");
+                testStatus = true;
+            } else {
+                printf("The solution vector is:\n");
+                for (psS32 i = 0 ; i < solution->n ; i++) {
+                    printf("(%.2f) ", solution->data.F64[i]);
+                }
+                printf("\n");
+
+                //-------------------------------------------------------------------------
+                printf("Calling pmSubtractionRejectStamps() with acceptable input parameters.  Should return TRUE.\n");
+                rc = pmSubtractionRejectStamps(stamps, maskImg, 0xff, TST03_FOOTPRINT, 1.0, refImg,
+                                               tstImg, solution, myKernels);
+                if (rc != TRUE) {
+                    printf("TEST ERROR: pmSubtractionRejectStamps() returned FALSE.\n");
+                    testStatus = true;
+                } else {}
+
+                //-------------------------------------------------------------------------
+                printf("Calling pmSubtractionKernelImage() with NULL solution.  Should generate error, return NULL.\n");
+                psImage *kernelImg = pmSubtractionKernelImage(NULL, NULL, myKernels, 0.0, 0.0);
+                if (kernelImg != NULL) {
+                    printf("TEST ERROR: pmSubtractionKernelImage() returned non-NULL.\n");
+                    testStatus = true;
+                }
+                free(kernelImg);
+
+                //-------------------------------------------------------------------------
+                printf("Calling pmSubtractionKernelImage() with NULL kernels.  Should generate error, return NULL.\n");
+                kernelImg = pmSubtractionKernelImage(NULL, solution, NULL, 0.0, 0.0);
+                if (kernelImg != NULL) {
+                    printf("TEST ERROR: pmSubtractionKernelImage() returned non-NULL.\n");
+                    testStatus = true;
+                }
+                free(kernelImg);
+
+                //-------------------------------------------------------------------------
+                printf("Calling pmSubtractionKernelImage() unallowable x value.  Should generate error, return NULL.\n");
+                kernelImg = pmSubtractionKernelImage(NULL, solution, myKernels, -2.0, 0.0);
+                if (kernelImg != NULL) {
+                    printf("TEST ERROR: pmSubtractionKernelImage() returned non-NULL.\n");
+                    testStatus = true;
+                }
+                free(kernelImg);
+
+                //-------------------------------------------------------------------------
+                printf("Calling pmSubtractionKernelImage() unallowable x value.  Should generate error, return NULL.\n");
+                kernelImg = pmSubtractionKernelImage(NULL, solution, myKernels, 2.0, 0.0);
+                if (kernelImg != NULL) {
+                    printf("TEST ERROR: pmSubtractionKernelImage() returned non-NULL.\n");
+                    testStatus = true;
+                }
+                free(kernelImg);
+
+                //-------------------------------------------------------------------------
+                printf("Calling pmSubtractionKernelImage() unallowable y value.  Should generate error, return NULL.\n");
+                kernelImg = pmSubtractionKernelImage(NULL, solution, myKernels, 0.0, -2.0);
+                if (kernelImg != NULL) {
+                    printf("TEST ERROR: pmSubtractionKernelImage() returned non-NULL.\n");
+                    testStatus = true;
+                }
+                free(kernelImg);
+
+                //-------------------------------------------------------------------------
+                printf("Calling pmSubtractionKernelImage() unallowable y value.  Should generate error, return NULL.\n");
+                kernelImg = pmSubtractionKernelImage(NULL, solution, myKernels, 0.0, 2.0);
+                if (kernelImg != NULL) {
+                    printf("TEST ERROR: pmSubtractionKernelImage() returned non-NULL.\n");
+                    testStatus = true;
+                }
+                free(kernelImg);
+
+                //-------------------------------------------------------------------------
+                printf("Calling pmSubtractionKernelImage() with acceptable input parameters.  Should return a psImage.\n");
+                kernelImg = pmSubtractionKernelImage(NULL, solution, myKernels, 0.5, 0.5);
+                if (kernelImg == NULL) {
+                    printf("TEST ERROR: pmSubtractionKernelImage() returned NULL.\n");
+                    testStatus = true;
+                } else {
+                    for (psS32 row = 0 ; row < kernelImg->numRows; row++) {
+                        for (psS32 col = 0 ; col < kernelImg->numCols; col++) {
+                            printf("%f ", kernelImg->data.F32[row][col]);
+                        }
+                        printf("\n");
+                    }
+                }
+                free(kernelImg);
+
+                psFree(solution);
+            }
+        }
+    }
+    psFree(tstImg);
+    psFree(refImg);
+    psFree(stamps);
+    psFree(myKernels);
+    psFree(orders);
+    psFree(sigmas);
+
+    return(testStatus);
+}
+
+/*******************************************************************************
+NOTE: This function returns TRUE if there were no errors.
+ ******************************************************************************/
+int test03( void )
+{
+    bool testStatus = false;
+
+
+    srand(1995);
+    if (1)
+        testStatus|= testSubCalcEqu(TST03_NUM_COLS,
+                                    TST03_NUM_ROWS,
+                                    TST03_NUM_STAMPS_COLS,
+                                    TST03_NUM_STAMPS_ROWS,
+                                    TST03_BORDER,
+                                    PM_SUBTRACTION_KERNEL_POIS);
+
+
+    srand(1995);
+    if (1)
+        testStatus|= testSubCalcEqu(TST03_NUM_COLS,
+                                    TST03_NUM_ROWS,
+                                    TST03_NUM_STAMPS_COLS,
+                                    TST03_NUM_STAMPS_ROWS,
+                                    TST03_BORDER,
+                                    PM_SUBTRACTION_KERNEL_ISIS);
+
+
+
+    return(!testStatus);
+}
Index: /tags/RHL_v1_1/psModules/test/imcombine/tst_pmReadoutCombine.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/imcombine/tst_pmReadoutCombine.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/imcombine/tst_pmReadoutCombine.c	(revision 9533)
@@ -0,0 +1,462 @@
+/** @file tst_pmReadoutCombine.c
+ *
+ *  test00() This routine will test the basic functionality.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-03-04 01:01:34 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ *  XXX: Untested:
+ * S16, S32 types
+ * Multiple input readouts with varying sizes and offsets.
+ * params->fracLow and params->fracHigh
+ * params->nKeep
+ * (gain > 0.0) && (readnoise >= 0.0) (applyZeroScale == true)
+ * (gain > 0.0) && (readnoise >= 0.0) (applyZeroScale == false)
+ *
+ */
+
+#include "psTest.h"
+#include "pslib.h"
+#include "pmReadoutCombine.h"
+static int test00(void);
+static int test01(void);
+testDescription tests[] = {
+                              {test00, 000, "pmSubtractBias(): Basic readout combines with no image overlap", true, false},
+                              {test01, 000, "pmSubtractBias(): input parameter error conditions", true, false},
+                              {NULL}
+                          };
+
+#define NUM_READOUTS  10
+#define INPUT_NUM_ROWS 20
+#define INPUT_NUM_COLS 20
+#define VEC_ZERO 1.0
+#define VEC_SCALE 2.0
+
+psS32 VerifyTheOutput(psImage *output, psF32 expect)
+{
+    bool testStatus = true;
+
+    for (psS32 i = 0 ; i < output->numRows ; i++) {
+        for (psS32 j = 0 ; j < output->numCols ; j++) {
+            if (output->data.F32[i][j] != expect) {
+                printf("TEST ERROR: output[%d][%d] is %.2f, should be %f\n", i, j, output->data.F32[i][j], expect);
+                testStatus = false;
+            }
+        }
+    }
+    return(testStatus);
+}
+
+
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+/******************************************************************************
+simpleCombineNoOverlap(): this routine creates a list of NUM_READOUTS input
+readouts and calls pmReadoutCombine().
+ *****************************************************************************/
+int simpleCombineNoOverlap(psS32 numInputCols, psS32 numInputRows)
+{
+    int i;
+    int r;
+    psList *list = NULL;
+    int baseRowsReadout[NUM_READOUTS];
+    int baseColsReadout[NUM_READOUTS];
+    int baseRows[NUM_READOUTS];
+    int baseCols[NUM_READOUTS];
+    int numRows[NUM_READOUTS];
+    int numCols[NUM_READOUTS];
+    int minOutRow = 10000;
+    int minOutCol = 10000;
+    int maxOutRow = -1;
+    int maxOutCol = -1;
+    psImage *output = NULL;
+    psCombineParams *params = (psCombineParams *) psAlloc(sizeof(psCombineParams));
+    psVector *zero = psVectorAlloc(NUM_READOUTS, PS_TYPE_F32);
+    psVector *scale = psVectorAlloc(NUM_READOUTS, PS_TYPE_F32);
+    zero->n = zero->nalloc;
+    scale->n = scale->nalloc;
+    printPositiveTestHeader(stdout, "pmReadoutCombine", "simpleCombineNoOverlap");
+
+    for (i=0;i<NUM_READOUTS;i++) {
+        zero->data.F32[i] = VEC_ZERO;
+    }
+    for (i=0;i<NUM_READOUTS;i++) {
+        scale->data.F32[i] = VEC_SCALE;
+    }
+
+    params->stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    params->maskVal = 1;
+    params->fracLow = 0.0;
+    params->fracHigh = 10000.0;
+    params->nKeep = 0;
+
+    //
+    // Create a psList of psReadouts.  The pixels in readout r will all have the
+    // value r.
+    //
+    for (r=0;r<NUM_READOUTS;r++) {
+        baseRowsReadout[r] = r + 40;
+        baseColsReadout[r] = r + 42;
+        baseRows[r] = r;
+        baseCols[r] = r+2;
+        numRows[r] = 4 + (2 * r);
+        numCols[r] = 8 + (2 * r);
+
+        baseRowsReadout[r] = 0;
+        baseColsReadout[r] = 0;
+        baseRows[r] = 0;
+        baseCols[r] = 0;
+        numRows[r] = numInputRows;
+        numCols[r] = numInputCols;
+
+        psImage *tmpImage = psImageAlloc(numCols[r], numRows[r], PS_TYPE_F32);
+        PS_IMAGE_SET_F32(tmpImage, ((float) r));
+        *(int *) (& (tmpImage->row0)) = baseRows[r];
+        *(int *) (& (tmpImage->col0)) = baseCols[r];
+        pmReadout *tmpReadout = pmReadoutAlloc(NULL);
+        tmpReadout->row0 = 0;
+        tmpReadout->col0 = 0;
+        tmpReadout->image = tmpImage;
+
+        minOutRow = PS_MIN(minOutRow, (baseRowsReadout[r] + baseRows[r]));
+        minOutCol = PS_MIN(minOutCol, (baseColsReadout[r] + baseCols[r]));
+        maxOutRow = PS_MAX(maxOutRow, (baseRowsReadout[r] + baseRows[r] + numRows[r]));
+        maxOutCol = PS_MAX(maxOutCol, (baseColsReadout[r] + baseCols[r] + numCols[r]));
+
+        if (r == 0) {
+            list = psListAlloc(tmpReadout);
+        } else {
+            psListAdd(list, PS_LIST_HEAD, tmpReadout);
+        }
+    }
+    printf("tst_pmReadoutCombine(): (minOutRow, minOutCol) to (maxOutRow, maxOutCol) is (%d, %d) (%d, %d)\n",
+           minOutRow, minOutCol, maxOutRow, maxOutCol);
+
+    output = pmReadoutCombine(output, list, params, zero, scale, true, 0.0, 0.0);
+    psF32 NR = (psF32) NUM_READOUTS;
+    psF32 expectedPixel = ((NR/2.0) * (VEC_ZERO + (VEC_ZERO + VEC_SCALE * (NR - 1)))) / NR;
+
+    int testStatus = VerifyTheOutput(output, expectedPixel);
+
+    psFree(params->stats);
+    psFree(params);
+    psFree(output);
+    psFree(zero);
+    psFree(scale);
+
+    psListElem *tmpInput = (psListElem *) list->head;
+    while (NULL != tmpInput) {
+        pmReadout *tmpReadout = (pmReadout *) tmpInput->data;
+        psFree(tmpReadout);
+        tmpInput = tmpInput->next;
+    }
+    psFree(list);
+
+    printFooter(stdout, "pmReadoutCombine", "simpleCombineNoOverlap", true);
+    return(testStatus);
+}
+
+int test00( void )
+{
+    int testStatus = 0;
+
+    testStatus |= simpleCombineNoOverlap(1, 1);
+    testStatus |= simpleCombineNoOverlap(INPUT_NUM_COLS, 1);
+    testStatus |= simpleCombineNoOverlap(1, INPUT_NUM_ROWS);
+    testStatus |= simpleCombineNoOverlap(INPUT_NUM_COLS, INPUT_NUM_ROWS);
+
+    return(testStatus);
+}
+
+/******************************************************************************
+test01(): we simply call pmReadoutCombine() with a variety of erroneous input
+parameter combinations and verify that it behaves properly.
+ *****************************************************************************/
+int test01()
+{
+    int testStatus = true;
+    int i;
+    int r;
+    psList *list = NULL;
+    int baseRowsReadout[NUM_READOUTS];
+    int baseColsReadout[NUM_READOUTS];
+    int baseRows[NUM_READOUTS];
+    int baseCols[NUM_READOUTS];
+    int numRows[NUM_READOUTS];
+    int numCols[NUM_READOUTS];
+    int minOutRow = 10000;
+    int minOutCol = 10000;
+    int maxOutRow = -1;
+    int maxOutCol = -1;
+    psImage *output = NULL;
+    psImage *rc = NULL;
+    psCombineParams *params = (psCombineParams *) psAlloc(sizeof(psCombineParams));
+    psVector *zero = psVectorAlloc(NUM_READOUTS, PS_TYPE_F32);
+    psVector *zeroHalf = psVectorAlloc(NUM_READOUTS/2, PS_TYPE_F32);
+    psVector *zeroBig = psVectorAlloc(NUM_READOUTS+1, PS_TYPE_F32);
+    psVector *zeroF64 = psVectorAlloc(NUM_READOUTS, PS_TYPE_F64);
+    psVector *scale = psVectorAlloc(NUM_READOUTS, PS_TYPE_F32);
+    psVector *scaleHalf = psVectorAlloc(NUM_READOUTS/2, PS_TYPE_F32);
+    psVector *scaleBig = psVectorAlloc(NUM_READOUTS*2, PS_TYPE_F32);
+    psVector *scaleF64 = psVectorAlloc(NUM_READOUTS, PS_TYPE_F64);
+    zero->n = zero->nalloc;
+    zeroHalf->n = zeroHalf->nalloc;
+    zeroBig->n = zeroBig->nalloc;
+    zeroF64->n = zeroF64->nalloc;
+    scale->n = scale->nalloc;
+    scaleHalf->n = scaleHalf->nalloc;
+    scaleBig->n = scaleBig->nalloc;
+    scaleF64->n = scaleF64->nalloc;
+    for (i=0;i<NUM_READOUTS;i++) {
+        zero->data.F32[i] = 3.0;
+        zeroBig->data.F32[i] = 3.0;
+        zero->data.F32[i] = VEC_ZERO;
+        zeroBig->data.F32[i] = VEC_ZERO;
+    }
+    for (i=0;i<NUM_READOUTS;i++) {
+        scale->data.F32[i] = 6.0;
+        scaleBig->data.F32[i] = 6.0;
+        scale->data.F32[i] = VEC_SCALE;
+        scaleBig->data.F32[i] = VEC_SCALE;
+    }
+    printPositiveTestHeader(stdout, "pmReadoutCombine", "Testing bad input parameter conditions");
+
+    params->stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    params->maskVal = 1;
+    params->fracLow = 0.0;
+    params->fracHigh = 10000.0;
+    params->nKeep = 0;
+
+    for (r=0;r<NUM_READOUTS;r++) {
+        baseRowsReadout[r] = 0;
+        baseColsReadout[r] = 0;
+        baseRows[r] = 0;
+        baseCols[r] = 0;
+        numRows[r] = INPUT_NUM_ROWS;
+        numCols[r] = INPUT_NUM_COLS;
+
+        psImage *tmpImage = psImageAlloc(numCols[r], numRows[r], PS_TYPE_F32);
+        PS_IMAGE_SET_F32(tmpImage, ((float) r));
+        *(int *) (& (tmpImage->row0)) = baseRows[r];
+        *(int *) (& (tmpImage->col0)) = baseCols[r];
+        pmReadout *tmpReadout = pmReadoutAlloc(NULL);
+        tmpReadout->row0 = 0;
+        tmpReadout->col0 = 0;
+        tmpReadout->image = tmpImage;
+        minOutRow = PS_MIN(minOutRow, (baseRowsReadout[r] + baseRows[r]));
+        minOutCol = PS_MIN(minOutCol, (baseColsReadout[r] + baseCols[r]));
+        maxOutRow = PS_MAX(maxOutRow, (baseRowsReadout[r] + baseRows[r] + numRows[r]));
+        maxOutCol = PS_MAX(maxOutCol, (baseColsReadout[r] + baseCols[r] + numCols[r]));
+
+        if (r == 0) {
+            list = psListAlloc(tmpReadout);
+        } else {
+            psListAdd(list, PS_LIST_HEAD, tmpReadout);
+        }
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with NULL zero vector.\n");
+    rc = pmReadoutCombine(NULL, list, params, NULL, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        psF32 NR = (psF32) NUM_READOUTS;
+        psF32 expectedPixel = ((NR/2.0) * (0.0 + (0.0 + VEC_SCALE * (NR - 1)))) / NR;
+        if (false == VerifyTheOutput(rc, expectedPixel)) {
+            testStatus = false;
+        }
+
+        if (rc->type.type != scale->type.type) {
+            printf("TEST ERROR: output readout->image has incorrect type.\n");
+            testStatus = false;
+        }
+        psFree(rc);
+    } else {
+        printf("TEST ERROR: pmReadoutCombine() returned NULL\n");
+        testStatus = false;
+
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with incorrect length zero vector (too small).  Should generate error.\n");
+    rc = pmReadoutCombine(NULL, list, params, zeroHalf, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with incorrect type zero vector.  Should generate error.\n");
+    rc = pmReadoutCombine(NULL, list, params, zeroF64, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with incorrect length zero vector (too big).  Should generate warning.\n");
+    rc = pmReadoutCombine(output, list, params, zeroBig, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        psF32 NR = (psF32) NUM_READOUTS;
+        psF32 expectedPixel = ((NR/2.0) * (VEC_ZERO + (VEC_ZERO + VEC_SCALE * (NR - 1)))) / NR;
+
+        if (false == VerifyTheOutput(rc, expectedPixel)) {
+            testStatus = false;
+        }
+        psFree(rc);
+        rc = NULL;
+    } else {
+        printf("TEST ERROR: pmReadoutCombine() returned NULL\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with NULL scale vector.\n");
+    rc = pmReadoutCombine(output, list, params, zero, NULL, true, 0.0, 0.0);
+
+    if (rc != NULL) {
+        psF32 NR = (psF32) NUM_READOUTS;
+        psF32 expectedPixel = ((NR/2.0) * (VEC_ZERO + (VEC_ZERO + 1.0 * (NR - 1)))) / NR;
+        if (false == VerifyTheOutput(rc, expectedPixel)) {
+            testStatus = false;
+        }
+
+        if (rc->type.type != scale->type.type) {
+            printf("TEST ERROR: output readout->image has incorrect type.\n");
+            testStatus = false;
+        }
+        psFree(rc);
+    } else {
+        printf("TEST ERROR: pmReadoutCombine() returned NULL\n");
+        testStatus = false;
+
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with incorrect length scale vector (too small).  Should generate error.\n");
+    rc = pmReadoutCombine(output, list, params, zero, scaleHalf, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with incorrect type scale vector.  Should generate error.\n");
+    rc = pmReadoutCombine(output, list, params, zero, scaleF64, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with incorrect length scale vector (too big).  Should generate warning.\n");
+    rc = pmReadoutCombine(output, list, params, zero, scaleBig, true, 0.0, 0.0);
+    if (rc != NULL) {
+        psF32 NR = (psF32) NUM_READOUTS;
+        psF32 expectedPixel = ((NR/2.0) * (VEC_ZERO + (VEC_ZERO + VEC_SCALE * (NR - 1)))) / NR;
+
+        if (false == VerifyTheOutput(rc, expectedPixel)) {
+            testStatus = false;
+        }
+        psFree(rc);
+        rc = NULL;
+    } else {
+        printf("TEST ERROR: pmReadoutCombine() returned NULL\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() insufficient size output image.  Should generate error, return NULL.\n");
+    output = psImageAlloc(1, 1, PS_TYPE_F32);
+    rc = pmReadoutCombine(output, list, params, zero, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+    psFree(output);
+    output = NULL;
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() row0/col0 too large.  Should generate error, return NULL.\n");
+    output = psImageAlloc(1, 1, PS_TYPE_F32);
+    *(psS32*)&output->row0 = 10000;
+    *(psS32*)&output->col0 = 10000;
+    rc = pmReadoutCombine(output, list, params, zero, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+    psFree(output);
+    output = NULL;
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with NULL input list.  Should generate error, return NULL.\n");
+    rc = pmReadoutCombine(output, NULL, params, zero, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("Calling pmReadoutCombine() with NULL params.  Should generate error, return NULL.\n");
+    rc = pmReadoutCombine(output, list, NULL, zero, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------\n");
+    psStatsOptions oldStatsOpts = params->stats->options |= PS_STAT_MIN;
+    printf("Calling pmReadoutCombine() with multiple stats->options.  Should generate error, return NULL.\n");
+    rc = pmReadoutCombine(output, list, params, zero, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+    params->stats->options = oldStatsOpts;
+
+    printf("----------------------------------------------------------------------------\n");
+    psStats *oldStats = params->stats;
+    printf("Calling pmReadoutCombine() with NULL param->stats.  Should generate error, return NULL.\n");
+    params->stats = NULL;
+    rc = pmReadoutCombine(output, list, params, zero, scale, true, 0.0, 0.0);
+    if (rc != NULL) {
+        printf("TEST ERROR: pmReadoutCombine() did not return NULL\n");
+        testStatus = false;
+    }
+    params->stats = oldStats;
+
+    printf("----------------------------------------------------------------------------\n");
+    printf("============================================================================\n");
+
+    psFree(params->stats);
+    psFree(params);
+    //    psFree(output);
+    //    psFree(rc);
+    psFree(zero);
+    psFree(zeroHalf);
+    psFree(zeroBig);
+    psFree(zeroF64);
+    psFree(scale);
+    psFree(scaleHalf);
+    psFree(scaleBig);
+    psFree(scaleF64);
+    psListElem *tmpInput = (psListElem *) list->head;
+    while (NULL != tmpInput) {
+        pmReadout *tmpReadout = (pmReadout *) tmpInput->data;
+        psFree(tmpReadout);
+        tmpInput = tmpInput->next;
+    }
+    psFree(list);
+
+    printFooter(stdout, "pmReadoutCombine", "Testing bad input parameter conditions", true);
+    return(testStatus);
+}
Index: /tags/RHL_v1_1/psModules/test/imcombine/verified/tst_pmImageCombine.stderr
===================================================================
--- /tags/RHL_v1_1/psModules/test/imcombine/verified/tst_pmImageCombine.stderr	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/imcombine/verified/tst_pmImageCombine.stderr	(revision 9533)
@@ -0,0 +1,25 @@
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmImageCombine.c                                       *
+*            TestPoint: Test Point Driver{pmCombineImages()}                       *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|E|pmCombineImages (FILE:LINENO)
+    Unallowable operation: images is NULL.
+<HOST>|E|pmCombineImages (FILE:LINENO)
+    images and errors args must have same length (6 != 5)
+<HOST>|E|pmCombineImages (FILE:LINENO)
+    Unallowable operation: psImage tmpDataImg has incorrect type.
+<HOST>|E|pmCombineImages (FILE:LINENO)
+    images and errors args must have same length (5 != 6)
+<HOST>|E|pmCombineImages (FILE:LINENO)
+    Unallowable operation: psImage tmpErrorImg has incorrect type.
+<HOST>|E|pmCombineImages (FILE:LINENO)
+    images and masks args must have same length (5 != 6)
+<HOST>|E|pmCombineImages (FILE:LINENO)
+    Unallowable operation: psImage tmpMaskImg has incorrect type.
+<HOST>|E|pmCombineImages (FILE:LINENO)
+    Unallowable operation: stats is NULL.
+
+---> TESTPOINT PASSED (Test Point Driver{pmCombineImages()} | tst_pmImageCombine.c)
+
Index: /tags/RHL_v1_1/psModules/test/imcombine/verified/tst_pmImageCombine.stdout
===================================================================
--- /tags/RHL_v1_1/psModules/test/imcombine/verified/tst_pmImageCombine.stdout	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/imcombine/verified/tst_pmImageCombine.stdout	(revision 9533)
@@ -0,0 +1,56 @@
+Testing pmCombineImages(10, 10, 5)
+Generating a bad pixel in image (1) at (0, 8)
+Generating a bad pixel in image (3) at (7, 5)
+Generating a bad pixel in image (1) at (1, 2)
+Generating a bad pixel in image (2) at (9, 9)
+Generating a bad pixel in image (1) at (3, 9)
+Generating a bad pixel in image (2) at (0, 5)
+Generating a bad pixel in image (1) at (4, 5)
+Generating a bad pixel in image (1) at (3, 8)
+Generating a bad pixel in image (2) at (6, 2)
+Generating a bad pixel in image (2) at (3, 7)
+Calling with a NULL images.  Should generate error, return NULL.
+Calling with a long images.  Should generate error, return NULL.
+Calling with a bad type images.  Should generate error, return NULL.
+Calling with a long errors.  Should generate error, return NULL.
+Calling with a bad type errors.  Should generate error, return NULL.
+Calling with a long masks.  Should generate error, return NULL.
+Calling with a bad type masks.  Should generate error, return NULL.
+Calling with a NULL stats.  Should generate error, return NULL.
+Calling with acceptable data.  Should generate a psImage.
+Image 0, questionable pixel 0 is (1.000000 2.000000)
+Image 0, questionable pixel 1 is (4.000000 5.000000)
+Image 1, questionable pixel 0 is (0.000000 8.000000)
+Image 1, questionable pixel 1 is (1.000000 2.000000)
+Image 1, questionable pixel 2 is (9.000000 9.000000)
+Image 1, questionable pixel 3 is (3.000000 9.000000)
+Image 1, questionable pixel 4 is (4.000000 5.000000)
+Image 1, questionable pixel 5 is (3.000000 8.000000)
+Image 2, questionable pixel 0 is (1.000000 2.000000)
+Image 2, questionable pixel 1 is (9.000000 9.000000)
+Image 2, questionable pixel 2 is (0.000000 5.000000)
+Image 2, questionable pixel 3 is (6.000000 2.000000)
+Image 2, questionable pixel 4 is (3.000000 7.000000)
+Image 3, questionable pixel 0 is (7.000000 5.000000)
+Image 4, questionable pixel 0 is (3.000000 8.000000)
+
+
+
+Calling pmRejectPixels() with acceptable data.  Should generate a psArray.
+tst_pmImageCombine.c: Image 0 had 2 rejects.
+Image 0, rejected pixel 0 is (1.000000 2.000000)
+Image 0, rejected pixel 1 is (4.000000 5.000000)
+tst_pmImageCombine.c: Image 1 had 4 rejects.
+Image 1, rejected pixel 0 is (0.000000 8.000000)
+Image 1, rejected pixel 1 is (1.000000 2.000000)
+Image 1, rejected pixel 2 is (4.000000 5.000000)
+Image 1, rejected pixel 3 is (3.000000 8.000000)
+tst_pmImageCombine.c: Image 2 had 4 rejects.
+Image 2, rejected pixel 0 is (1.000000 2.000000)
+Image 2, rejected pixel 1 is (0.000000 5.000000)
+Image 2, rejected pixel 2 is (6.000000 2.000000)
+Image 2, rejected pixel 3 is (3.000000 7.000000)
+tst_pmImageCombine.c: Image 3 had 1 rejects.
+Image 3, rejected pixel 0 is (7.000000 5.000000)
+tst_pmImageCombine.c: Image 4 had 1 rejects.
+Image 4, rejected pixel 0 is (3.000000 8.000000)
Index: /tags/RHL_v1_1/psModules/test/imcombine/verified/tst_pmReadoutCombine.stderr
===================================================================
--- /tags/RHL_v1_1/psModules/test/imcombine/verified/tst_pmReadoutCombine.stderr	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/imcombine/verified/tst_pmReadoutCombine.stderr	(revision 9533)
@@ -0,0 +1,42 @@
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmReadoutCombine.c                                     *
+*            TestPoint: Test Point Driver{pmSubtractBias(): Basic readout combines with no image overla *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (Test Point Driver{pmSubtractBias(): Basic readout combines with no image overlap} | tst_pmReadoutCombine.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmReadoutCombine.c                                     *
+*            TestPoint: Test Point Driver{pmSubtractBias(): input parameter error conditions} *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|E|pmReadoutCombine (FILE:LINENO)
+    zero vector has incorrect size (5).  Returning NULL.
+<HOST>|E|pmReadoutCombine (FILE:LINENO)
+    Unallowable operation: psVector zero has incorrect type.
+<HOST>|W|pmReadoutCombine
+    WARNING: the zero vector too many elements (11)
+<HOST>|E|pmReadoutCombine (FILE:LINENO)
+    scale vector has incorrect size (5).  Returning NULL.
+<HOST>|E|pmReadoutCombine (FILE:LINENO)
+    Unallowable operation: psVector scale has incorrect type.
+<HOST>|W|pmReadoutCombine
+    WARNING: the scale vector has too many elements (20)
+<HOST>|E|pmReadoutCombine (FILE:LINENO)
+    Output image (1, 1) is too small to hold combined images.  Returning NULL.
+<HOST>|E|pmReadoutCombine (FILE:LINENO)
+    Output image offset is larger then input image offset.  Returning NULL.
+<HOST>|E|pmReadoutCombine (FILE:LINENO)
+    Unallowable operation: inputs is NULL.
+<HOST>|E|pmReadoutCombine (FILE:LINENO)
+    Unallowable operation: params is NULL.
+<HOST>|E|pmReadoutCombine (FILE:LINENO)
+    Multiple statistical options have been requested.  Returning NULL.
+<HOST>|E|pmReadoutCombine (FILE:LINENO)
+    Unallowable operation: params->stats is NULL.
+
+---> TESTPOINT PASSED (Test Point Driver{pmSubtractBias(): input parameter error conditions} | tst_pmReadoutCombine.c)
+
Index: /tags/RHL_v1_1/psModules/test/imcombine/verified/tst_pmReadoutCombine.stdout
===================================================================
--- /tags/RHL_v1_1/psModules/test/imcombine/verified/tst_pmReadoutCombine.stdout	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/imcombine/verified/tst_pmReadoutCombine.stdout	(revision 9533)
@@ -0,0 +1,79 @@
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmReadoutCombine.c                                     *
+*            TestPoint: pmReadoutCombine{simpleCombineNoOverlap}                   *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+tst_pmReadoutCombine(): (minOutRow, minOutCol) to (maxOutRow, maxOutCol) is (0, 0) (1, 1)
+
+---> TESTPOINT PASSED (pmReadoutCombine{simpleCombineNoOverlap} | tst_pmReadoutCombine.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmReadoutCombine.c                                     *
+*            TestPoint: pmReadoutCombine{simpleCombineNoOverlap}                   *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+tst_pmReadoutCombine(): (minOutRow, minOutCol) to (maxOutRow, maxOutCol) is (0, 0) (1, 20)
+
+---> TESTPOINT PASSED (pmReadoutCombine{simpleCombineNoOverlap} | tst_pmReadoutCombine.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmReadoutCombine.c                                     *
+*            TestPoint: pmReadoutCombine{simpleCombineNoOverlap}                   *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+tst_pmReadoutCombine(): (minOutRow, minOutCol) to (maxOutRow, maxOutCol) is (0, 0) (20, 1)
+
+---> TESTPOINT PASSED (pmReadoutCombine{simpleCombineNoOverlap} | tst_pmReadoutCombine.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmReadoutCombine.c                                     *
+*            TestPoint: pmReadoutCombine{simpleCombineNoOverlap}                   *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+tst_pmReadoutCombine(): (minOutRow, minOutCol) to (maxOutRow, maxOutCol) is (0, 0) (20, 20)
+
+---> TESTPOINT PASSED (pmReadoutCombine{simpleCombineNoOverlap} | tst_pmReadoutCombine.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmReadoutCombine.c                                     *
+*            TestPoint: pmReadoutCombine{Testing bad input parameter conditions}   *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+----------------------------------------------------------------------------
+Calling pmReadoutCombine() with NULL zero vector.
+----------------------------------------------------------------------------
+Calling pmReadoutCombine() with incorrect length zero vector (too small).  Should generate error.
+----------------------------------------------------------------------------
+Calling pmReadoutCombine() with incorrect type zero vector.  Should generate error.
+----------------------------------------------------------------------------
+Calling pmReadoutCombine() with incorrect length zero vector (too big).  Should generate warning.
+----------------------------------------------------------------------------
+Calling pmReadoutCombine() with NULL scale vector.
+----------------------------------------------------------------------------
+Calling pmReadoutCombine() with incorrect length scale vector (too small).  Should generate error.
+----------------------------------------------------------------------------
+Calling pmReadoutCombine() with incorrect type scale vector.  Should generate error.
+----------------------------------------------------------------------------
+Calling pmReadoutCombine() with incorrect length scale vector (too big).  Should generate warning.
+----------------------------------------------------------------------------
+Calling pmReadoutCombine() insufficient size output image.  Should generate error, return NULL.
+----------------------------------------------------------------------------
+Calling pmReadoutCombine() row0/col0 too large.  Should generate error, return NULL.
+----------------------------------------------------------------------------
+Calling pmReadoutCombine() with NULL input list.  Should generate error, return NULL.
+----------------------------------------------------------------------------
+Calling pmReadoutCombine() with NULL params.  Should generate error, return NULL.
+----------------------------------------------------------------------------
+Calling pmReadoutCombine() with multiple stats->options.  Should generate error, return NULL.
+----------------------------------------------------------------------------
+Calling pmReadoutCombine() with NULL param->stats.  Should generate error, return NULL.
+----------------------------------------------------------------------------
+============================================================================
+
+---> TESTPOINT PASSED (pmReadoutCombine{Testing bad input parameter conditions} | tst_pmReadoutCombine.c)
+
Index: /tags/RHL_v1_1/psModules/test/objects/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/objects/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/objects/.cvsignore	(revision 9533)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmObjects01
Index: /tags/RHL_v1_1/psModules/test/objects/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/objects/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/objects/Makefile.am	(revision 9533)
@@ -0,0 +1,19 @@
+AM_LDFLAGS = -L$(top_builddir)/src -lpsmodules $(PSMODULES_LIBS)
+AM_CFLAGS  = @AM_CFLAGS@ $(PSMODULES_CFLAGS) $(SRCINC)
+
+TESTS = \
+    tst_pmObjects01
+
+tst_pmObjects01_SOURCES = tst_pmObjects01.c
+
+check_PROGRAMS = $(TESTS)
+
+TESTS_ENVIRONMENT = perl $(top_srcdir)/test/runTest --verified=$(srcdir)/verified
+
+tests: $(TESTS)
+
+EXTRA_DIST = verified
+
+CLEANFILES = $(TESTS) temp/* *~
+
+test: check
Index: /tags/RHL_v1_1/psModules/test/objects/tst_pmObjects01.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/objects/tst_pmObjects01.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/objects/tst_pmObjects01.c	(revision 9533)
@@ -0,0 +1,1300 @@
+/** @file tst_pmObjects.c
+ *
+ *  @brief Contains the tests for pmObjects.c:
+ *
+ * test00: This code will test the pmObjects routines.
+ *
+ *  @author GLG, MHPCC
+ *
+ * XXX: Must test
+ *       pmSourceRoughClass
+ *  many others...
+ *
+ *
+ * XXX: Must test output results for many other functions.
+ *
+ * XXX: There are many cases where row/col can be switched in the code.
+ * We must test that here by using non-square images.  All tests
+ * in this file should be run with non-square images.
+ *
+ * XXX: Memory leaks are not being caught.  If I allocated a psVector in these functions
+ * and never deallocate, no error is generated.
+ *
+ * XXX: Much of this file is commented out due to the API changes in rel 7.
+ *
+ *
+Fully Tested:
+    pmPeakAlloc()
+    pmMomentsAlloc()
+Weakly Tested:
+    pmSourceMoments()
+    most of psObjects.c is not tested
+ *
+ *  @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-03-04 01:01:34 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+#include "psTest.h"
+#include "pslib.h"
+#include "pmObjects.h"
+#include "pmModelGroup.h"
+
+#define NUM_ROWS 10
+#define NUM_COLS 10
+#define ERROR_TOLERANCE 1.0
+#define ERROR_TOL 0.001
+static int test00(void);
+static int test01(void);
+static int test02(void);
+static int test03(void);
+static int test04(void);
+static int test05(void);
+//static int test06(void);
+//static int test07(void);
+//static int test08(void);
+//static int test09(void);
+//static int test15(void);
+//static int test16(void);
+//static int test20(void);
+testDescription tests[] = {
+                              {test00, 000, "pmObjects: structure allocators and deallocators", true, false},
+                              {test01, 001, "pmObjects: psFindVectorPeaks()", true, false},
+                              {test02, 001, "pmObjects: psFindImagePeaks()", true, false},
+                              {test03, 001, "pmObjects: pmCullPeaks()", true, false},
+                              {test04, 001, "pmObjects: pmSourceLocalSky()", true, false},
+                              {test05, 001, "pmObjects: pmSourceMoments()", true, false},
+                              //                              {test06, 001, "pmObjects: pmSourceSetPixelsCircle()", true, false},
+                              //                              {test07, 001, "pmObjects: pmMin()", true, false},
+                              //                              {test08, 001, "pmObjects: pmSourceModelGuess()", true, false},
+                              //{test09, 001, "pmObjects: pmSourceContour()", true, false},
+                              //{test15, 001, "pmObjects: pmSourceAddModel()", true, false},
+                              //{test16, 001, "pmObjects: pmSourceSubModel()", true, false},
+                              //{test20, 001, "pmObjects: pmSourceSubModel()", true, false},
+                              {NULL}
+                          };
+
+int main(int argc, char* argv[])
+{
+    psLogSetFormat("HLNM");
+    //
+    // We include the function names here in psTraceSetLevel() commands for
+    // debugging convenience.  There is no guarantee that this list of functions
+    // is complete.
+    //
+    psTraceSetLevel(".", 0);
+    psTraceSetLevel("pmPeakAlloc", 0);
+    psTraceSetLevel("pmMomentsAlloc", 0);
+    psTraceSetLevel("modelFree", 0);
+    psTraceSetLevel("pmModelAlloc", 0);
+    psTraceSetLevel("sourceFree", 0);
+    psTraceSetLevel("pmSourceAlloc", 0);
+    psTraceSetLevel("pmFindVectorPeaks", 0);
+    psTraceSetLevel("getRowVectorFromImage", 0);
+    psTraceSetLevel("myListAddPeak", 0);
+    psTraceSetLevel("pmFindImagePeaks", 0);
+    psTraceSetLevel("isItInThisRegion", 0);
+    psTraceSetLevel("pmCullPeaks", 0);
+    psTraceSetLevel("pmPeaksSubset", 0);
+    psTraceSetLevel("pmSourceLocalSky", 0);
+    psTraceSetLevel("checkRadius2", 0);
+    psTraceSetLevel("pmSourceMoments", 0);
+    psTraceSetLevel("pmComparePeakAscend", 0);
+    psTraceSetLevel("pmComparePeakDescend", 0);
+    psTraceSetLevel("pmSourcePSFClump", 0);
+    psTraceSetLevel("pmSourceRoughClass", 0);
+    psTraceSetLevel("pmSourceDefinePixels", 0);
+    psTraceSetLevel("pmSourceModelGuess", 0);
+    psTraceSetLevel("pmModelEval", 0);
+    psTraceSetLevel("findValue", 0);
+    psTraceSetLevel("pmSourceContour", 0);
+    psTraceSetLevel("pmSourceFitModel_v5", 0);
+    psTraceSetLevel("pmSourceFitModel", 0);
+    psTraceSetLevel("p_pmSourceAddOrSubModel", 0);
+    psTraceSetLevel("pmSourceAddModel", 0);
+    psTraceSetLevel("pmSourceSubModel", 0);
+
+    return !runTestSuite(stderr, "Test Point Driver", tests, argc, argv);
+}
+
+/******************************************************************************
+test00(): Test the various allocators and deallocators.
+ *****************************************************************************/
+int test00( void )
+{
+    bool testStatus = true;
+    psTraceSetLevel(".", 0);
+
+    printf("Testing pmPeakAlloc()...\n");
+    pmPeak *tmpPeak = pmPeakAlloc(1, 2, 3.0, PM_PEAK_LONE);
+    if (tmpPeak == NULL) {
+        printf("TEST ERROR: pmPeakAlloc() returned a NULL pmPeak\n");
+        testStatus = false;
+    } else {
+        if (tmpPeak->x != 1) {
+            printf("TEST ERROR: pmPeakAlloc() improperly set pmPeak->x\n");
+            testStatus = false;
+        }
+        if (tmpPeak->y != 2) {
+            printf("TEST ERROR: pmPeakAlloc() improperly set pmPeak->y\n");
+            testStatus = false;
+        }
+        if (tmpPeak->counts != 3.0) {
+            printf("TEST ERROR: pmPeakAlloc() improperly set pmPeak->counts\n");
+            testStatus = false;
+        }
+        if (tmpPeak->class != PM_PEAK_LONE) {
+            printf("TEST ERROR: pmPeakAlloc() improperly set pmPeak->class\n");
+            testStatus = false;
+        }
+    }
+    psFree(tmpPeak);
+
+    printf("Testing pmMomentsAlloc()...\n");
+    pmMoments *tmpMoments = pmMomentsAlloc();
+    if (tmpMoments == NULL) {
+        printf("TEST ERROR: pmMomentsAlloc() returned a NULL pmMoments\n");
+        testStatus = false;
+    } else {
+        if ((fabs(tmpMoments->x-0.0) > ERROR_TOL) ||
+                (fabs(tmpMoments->y-0.0) > ERROR_TOL) ||
+                (fabs(tmpMoments->Sx-0.0) > ERROR_TOL) ||
+                (fabs(tmpMoments->Sy-0.0) > ERROR_TOL) ||
+                (fabs(tmpMoments->Sxy-0.0) > ERROR_TOL) ||
+                (fabs(tmpMoments->Sum-0.0) > ERROR_TOL) ||
+                (fabs(tmpMoments->Peak-0.0) > ERROR_TOL) ||
+                (fabs(tmpMoments->Sky-0.0) > ERROR_TOL) ||
+                (tmpMoments->nPixels != 0)) {
+            printf("TEST ERROR: pmMomentsAlloc() did not properly initialize the pmMoments structure.\n");
+            printf("    tmpMoments->x is %f\n", tmpMoments->x);
+            printf("    tmpMoments->y is %f\n", tmpMoments->y);
+            printf("    tmpMoments->Sx is %f\n", tmpMoments->Sx);
+            printf("    tmpMoments->Sy is %f\n", tmpMoments->Sy);
+            printf("    tmpMoments->Sxy is %f\n", tmpMoments->Sxy);
+            printf("    tmpMoments->Sum is %f\n", tmpMoments->Sum);
+            printf("    tmpMoments->Peak is %f\n", tmpMoments->Peak);
+            printf("    tmpMoments->Sky is %f\n", tmpMoments->Sky);
+            printf("    tmp    Moments->nPixels is %d\n", tmpMoments->nPixels);
+            testStatus = false;
+        }
+    }
+    psFree(tmpMoments);
+
+
+    //
+    // Loop through each type of model
+    //
+    psS32 i = 0;
+    while (0 != pmModelParameterCount(i)) {
+        printf("Testing pmModelAlloc(%s)...\n", pmModelGetType(0));
+        pmModel *tmpModel = pmModelAlloc(i);
+        if (tmpModel == NULL) {
+            printf("TEST ERROR: pmModelAlloc(%s) returned a NULL pmModel\n", pmModelGetType(0));
+            testStatus = false;
+        } else {
+
+            /* XXX: Should we test that the members were set correctly?
+                        if ((tmpModel->params->n != 7) || (tmpModel->dparams->n != 7)) {
+                            printf("TEST ERROR: pmModelAlloc(PS_MODEL_GAUSS) allocated an incorrect number of params (%ld, %ld)\n",
+                                   tmpModel->params->n, tmpModel->dparams->n);
+                            testStatus = false;
+                        } else {
+                            for (psS32 i = 0 ; i < 7 ; i++) {
+                                if ((tmpModel->params->data.F32[i] != 0.0) ||
+                                        (tmpModel->dparams->data.F32[i] != 0.0)) {
+                                    printf("TEST ERROR: pmModelAlloc(PS_MODEL_GAUSS) did not ininitialize the params/dparams array to 0.0.\n");
+                                    testStatus = false;
+                                }
+                            }
+                        }
+            */
+        }
+        psFree(tmpModel);
+        i++;
+    }
+
+
+    pmSource *tmpSource = pmSourceAlloc();
+    if (tmpSource == NULL) {
+        printf("TEST ERROR: pmSourceAlloc() returned a NULL pmSource\n");
+        testStatus = false;
+    }
+    psFree(tmpSource);
+
+    return(testStatus);
+}
+
+/******************************************************************************
+test01(): we first test pmFindVectorPeaks() with a variety of bad input
+parameters.  Then we test it with a simple vector both 1- and multi-elements.
+ *****************************************************************************/
+#define TST01_VECTOR_LENGTH 10
+bool test_pmFindVectorPeaks(int n)
+{
+    bool testStatus = true;
+    psVector *inData = psVectorAlloc(n, PS_TYPE_F32);
+    inData->n = inData->nalloc;
+    psVector *outData = NULL;
+
+    printf("-------------- Calling test_pmFindVectorPeaks on an %d size vector. --------------\n", n);
+    //
+    // Test first pixel peak.
+    //
+    printf("Test pmFindVectorPeaks() with a first-element peak.\n");
+    for (psS32 i = 0 ; i < n ; i++) {
+        inData->data.F32[i] = (float) (n-i);
+    }
+    inData->data.F32[0] = (float) n;
+
+    outData= pmFindVectorPeaks(inData, 0.0);
+    if (outData == NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+        if (outData->n != 1) {
+            printf("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+        if (outData->data.U32[0] != 0) {
+            printf("TEST ERROR: Did not find peak at element 0.\n");
+            testStatus = false;
+        }
+        psFree(outData);
+    }
+
+    //
+    // Test first pixel peak, large threshold
+    //
+    printf("Test pmFindVectorPeaks() with a first-element peak, large threshold.\n");
+    for (psS32 i = 0 ; i < n ; i++) {
+        inData->data.F32[i] = (float) (n-i);
+    }
+    inData->data.F32[0] = (float) n;
+
+    outData= pmFindVectorPeaks(inData, (float) (n*n));
+    if (outData == NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+
+        if (outData->n != 0) {
+            printf("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+        psFree(outData);
+
+        // Skip remaining tests if the input vector has length 1.
+        if (n == 1) {
+            psFree(inData);
+            return(testStatus);
+        }
+    }
+
+    //
+    // Test last pixel peak.
+    //
+    printf("Test pmFindVectorPeaks() with a last-element peak.\n");
+    for (psS32 i = 0 ; i < n ; i++) {
+        inData->data.F32[i] = (float) (i);
+    }
+    inData->data.F32[n-1] = (float) n;
+
+    outData= pmFindVectorPeaks(inData, 0.0);
+    if (outData == NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+
+        if (outData->n != 1) {
+            printf("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+        if (outData->data.U32[0] != n-1) {
+            printf("TEST ERROR: Did not find peak at element %d.\n", n-1);
+            testStatus = false;
+        }
+        psFree(outData);
+    }
+
+    //
+    // Test last pixel peak, large threshold.
+    //
+    printf("Test pmFindVectorPeaks() with a last-element peak, large threshold.\n");
+    for (psS32 i = 0 ; i < n ; i++) {
+        inData->data.F32[i] = (float) (i);
+    }
+    inData->data.F32[n-1] = (float) n;
+
+    outData= pmFindVectorPeaks(inData, (float) (n*n));
+    if (outData == NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+
+        if (outData->n != 0) {
+            printf("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+        psFree(outData);
+    }
+
+    //
+    // Test interior peaks.
+    // Set all even number elements to be peaks.
+    //
+    printf("Test pmFindVectorPeaks() with all even-numbered elements peak.\n");
+    for (psS32 i = 0 ; i < n ; i++) {
+        if (0 == i%2) {
+            inData->data.F32[i] = (float) (2 * i);
+        } else {
+            inData->data.F32[i] = (float) (i);
+        }
+    }
+    inData->data.F32[0] = (float) n;
+
+
+    outData= pmFindVectorPeaks(inData, 0.0);
+    if (outData == NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+
+        if (outData->n != n/2) {
+            printf("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+
+        for (psS32 i = 0 ; i < outData->n ; i++) {
+            if (outData->data.U32[i] != (2 * i)) {
+                printf("TEST ERROR: the %d-th peak is element number %d\n", i, outData->data.U32[i]);
+                testStatus = false;
+            }
+        }
+        psFree(outData);
+    }
+
+    //
+    // Test interior peaks, with threshold = n*n.
+    // Should generate an empty output psVector.
+    //
+    printf("Test pmFindVectorPeaks() with all even-numbered elements peak, large threshold.\n");
+    outData= pmFindVectorPeaks(inData, (float) (n*n));
+    if (outData == NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks returned a NULL psVector.\n");
+        testStatus = false;
+    } else {
+
+        if (outData->n != 0) {
+            printf("TEST ERROR: outData->n is %ld\n", outData->n);
+            testStatus = false;
+        }
+        psFree(outData);
+    }
+
+    psFree(inData);
+    return(testStatus);
+}
+
+int test01( void )
+{
+    bool testStatus = true;
+    psVector *tmpVec = NULL;
+    psVector *tmpVecF64 = psVectorAlloc(TST01_VECTOR_LENGTH, PS_TYPE_F64);
+    psVector *tmpVecEmpty = psVectorAlloc(0, PS_TYPE_F32);
+    tmpVecF64->n = tmpVecF64->nalloc;
+    tmpVecEmpty->n = tmpVecEmpty->nalloc;
+
+    psTraceSetLevel(".", 0);
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmFindVectorPeaks with NULL psVector.  Should generate error and return NULL.\n");
+    tmpVec = pmFindVectorPeaks(NULL, 0.0);
+    if (tmpVec != NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks() returned a non-NULL psVector.\n");
+        testStatus = false;
+        psFree(tmpVec);
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmFindVectorPeaks with empty psVector.  Should generate error and return NULL.\n");
+    tmpVec = pmFindVectorPeaks(tmpVecEmpty, 0.0);
+    if (tmpVec != NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks() returned a non-NULL psVector.\n");
+        testStatus = false;
+        psFree(tmpVec);
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmFindVectorPeaks with PS_TYPE_F64 psVector.  Should generate error and return NULL.\n");
+    tmpVec = pmFindVectorPeaks(tmpVecF64, 0.0);
+    if (tmpVec != NULL) {
+        printf("TEST ERROR: pmFindVectorPeaks() returned a non-NULL psVector.\n");
+        testStatus = false;
+        psFree(tmpVec);
+    }
+    testStatus&= test_pmFindVectorPeaks(1);
+    testStatus&= test_pmFindVectorPeaks(TST01_VECTOR_LENGTH);
+
+    psFree(tmpVecF64);
+    psFree(tmpVecEmpty);
+    return(testStatus);
+}
+
+/******************************************************************************
+test02():
+// XXX: Must test flat peaks.
+// XXX: test 1-by-n and n-by-1 images.
+ *****************************************************************************/
+#define TST02_NUM_ROWS 5
+#define TST02_NUM_COLS 5
+bool test_pmFindImagePeaks(int numRows, int numCols)
+{
+    printf("-------------- Calling test_pmFindImagePeaks on an %d-by-%d image. --------------\n", numRows, numCols);
+    //    if ((numRows < 4) || (numCols < 4)) {
+    //        printf("WARNING: Don't call this test with a smaller than 4-by-4 image.\n");
+    //        return(true);
+    //    }
+    bool testStatus = true;
+    psImage *inData = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+    psArray *outData = NULL;
+
+    //
+    // Initialize test image.
+    //
+    for (psS32 i = 0 ; i < numRows ; i++) {
+        for (psS32 j = 0 ; j < numCols ; j++) {
+            inData->data.F32[i][j] = PS_SQR(i - numRows/2) + PS_SQR(j-numCols/2);
+        }
+    }
+    //
+    // Set corner and center pixels as peaks.
+    //
+    inData->data.F32[0][0] = PS_SQR(numRows) + PS_SQR(numCols);
+    inData->data.F32[0][numCols-1] = PS_SQR(numRows) + PS_SQR(numCols);
+    inData->data.F32[numRows-1][0] = PS_SQR(numRows) + PS_SQR(numCols);
+    inData->data.F32[numRows-1][numCols-1] = PS_SQR(numRows) + PS_SQR(numCols);
+    inData->data.F32[numRows/2][numCols/2] = PS_SQR(numRows) + PS_SQR(numCols);
+
+    //
+    // Print image.
+    //
+    for (psS32 i = 0 ; i < numRows ; i++) {
+        for (psS32 j = 0 ; j < numCols ; j++) {
+            printf("(%.1f) ", inData->data.F32[i][j]);
+        }
+        printf("\n");
+    }
+
+    //
+    // Call pmFindImagePeaks() with a threshold of 0.0.
+    //
+    outData = pmFindImagePeaks(inData, 0.0);
+
+    if (outData == NULL) {
+        printf("TEST ERROR: pmFindImagePeaks returned a NULL psList.\n");
+        testStatus = false;
+    } else {
+        psS32 expectedNumPeaks;
+        if ((numRows == 1) && (numCols == 1)) {
+            expectedNumPeaks = 1;
+        } else if ((numRows == 1) || (numCols == 1)) {
+            expectedNumPeaks = 3;
+        } else {
+            expectedNumPeaks = 5;
+        }
+        if (outData->n != expectedNumPeaks) {
+            printf("TEST ERROR: pmFindImagePeaks found %ld peaks (should be %d)\n", outData->n, expectedNumPeaks);
+            testStatus = false;
+        }
+
+        // HEY: verify
+        for (psS32 i = 0 ; i < outData->n ; i++) {
+            pmPeak *tmpPeak = (pmPeak *) outData->data[i];
+            if (((tmpPeak->x == 0) && (tmpPeak->y == 0)) ||
+                    ((tmpPeak->x == 0) && (tmpPeak->y == numRows-1)) ||
+                    ((tmpPeak->x == numCols-1) && (tmpPeak->y == 0)) ||
+                    ((tmpPeak->x == numCols-1) && (tmpPeak->y == numRows-1))) {
+                if (!((tmpPeak->class & PM_PEAK_LONE) || (tmpPeak->class & PM_PEAK_EDGE))) {
+                    printf("TEST ERROR: (0) peak at (%d, %d) (%f) ->class set improperly (0x%x).",
+                           tmpPeak->y, tmpPeak->x, tmpPeak->counts, tmpPeak->class);
+                    printf(" should be (0x%x or 0x%x).\n", PM_PEAK_LONE, PM_PEAK_EDGE);
+                    testStatus = false;
+                }
+            } else if ((tmpPeak->x == numCols/2) && (tmpPeak->y == numRows/2)) {
+                if (tmpPeak->class != PM_PEAK_LONE) {
+                    printf("TEST ERROR: (1) peak at (%d, %d) (%f) ->class set improperly (0x%x).\n",
+                           tmpPeak->y, tmpPeak->x, tmpPeak->counts, tmpPeak->class);
+                    printf(" should be (0x%x).\n", PM_PEAK_LONE);
+                    testStatus = false;
+                }
+            } else {
+                printf("TEST ERROR: Peak at (%d, %d) (%f)\n", tmpPeak->y, tmpPeak->x, tmpPeak->counts);
+                testStatus = false;
+            }
+        }
+    }
+
+    psFree(inData);
+    psFree(outData);
+    return(testStatus);
+}
+
+int test02( void )
+{
+    bool testStatus = true;
+    psArray *tmpArray = NULL;
+    psImage *tmpImageF64 = psImageAlloc(TST02_NUM_ROWS, TST02_NUM_COLS, PS_TYPE_F64);
+    psImage *tmpImageEmpty = psImageAlloc(0, 0, PS_TYPE_F32);
+
+    psTraceSetLevel(".", 0);
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmFindImagePeaks with NULL psImage.  Should generate error and return NULL.\n");
+    tmpArray = pmFindImagePeaks(NULL, 0.0);
+    if (tmpArray != NULL) {
+        printf("TEST ERROR: pmFindImagePeaks() returned a non-NULL psImage.\n");
+        testStatus = false;
+        psFree(tmpArray);
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmFindImagePeaks with empty psImage.  Should generate error and return NULL.\n");
+    tmpArray = pmFindImagePeaks(tmpImageEmpty, 0.0);
+    if (tmpArray != NULL) {
+        printf("TEST ERROR: pmFindImagePeaks() returned a non-NULL psImage.\n");
+        testStatus = false;
+        psFree(tmpArray);
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmFindImagePeaks with PS_TYPE_F64 psImage.  Should generate error and return NULL.\n");
+    tmpArray = pmFindImagePeaks(tmpImageF64, 0.0);
+    if (tmpArray != NULL) {
+        printf("TEST ERROR: pmFindImagePeaks() returned a non-NULL psImage.\n");
+        testStatus = false;
+        psFree(tmpArray);
+    }
+    printf("----------------------------------------------------------------------------------\n");
+    //    testStatus&= test_pmFindImagePeaks(1, 1);
+    //    testStatus&= test_pmFindImagePeaks(2, 5);
+    //    testStatus&= test_pmFindImagePeaks(5, 2);
+    // HEY: add code for small images
+    //    testStatus&= test_pmFindImagePeaks(1, 1);
+    //    testStatus&= test_pmFindImagePeaks(1, 8);
+    //    testStatus&= test_pmFindImagePeaks(8, 1);
+    testStatus&= test_pmFindImagePeaks(TST02_NUM_ROWS,   TST02_NUM_COLS);
+    testStatus&= test_pmFindImagePeaks(2*TST02_NUM_ROWS, TST02_NUM_COLS);
+    testStatus&= test_pmFindImagePeaks(TST02_NUM_ROWS,   2*TST02_NUM_COLS);
+
+
+    psFree(tmpImageF64);
+    psFree(tmpImageEmpty);
+    return(testStatus);
+}
+
+/******************************************************************************
+test03(): We first test pmCullPeaks() with various NULL and unallowable input
+parameters.  Then we generate a list of peaks and test that pmCullPeaks()
+removes them correctly.
+ *****************************************************************************/
+int test03( void )
+{
+    bool testStatus = true;
+    psImage *imgData = psImageAlloc(NUM_COLS, NUM_ROWS, PS_TYPE_F32);
+    psArray *outData = NULL;
+
+    /* XXX: Modify for new pmCullPeaks()
+            printf("----------------------------------------------------------------------------------\n");
+            printf("Calling pmCullPeaks with NULL psList.  Should generate error and return NULL.\n");
+            outData = pmCullPeaks(NULL, 0.0, NULL);
+            if (outData != NULL) {
+                printf("TEST ERROR: pmCulPeaks() returned a non-NULL psList.\n");
+                testStatus = false;
+        }
+    */
+
+    //
+    // Set peaks in input image.  All even-column and even-row pixels are
+    // set non-zero, all other pixels are set to zero.
+    //
+    psS32 numPeaksOrig = 0;
+    for (psS32 i = 0 ; i < NUM_ROWS ; i++) {
+        for (psS32 j = 0 ; j < NUM_COLS ; j++) {
+            if ((0 == i%2) && (0 == j%2)) {
+                imgData->data.F32[i][j] = (float) (i + 10);
+                numPeaksOrig++;
+            } else {
+                imgData->data.F32[i][j] = 0.0;
+            }
+        }
+    }
+    for (psS32 i = 0 ; i < NUM_ROWS ; i++) {
+        for (psS32 j = 0 ; j < NUM_COLS ; j++) {
+            printf("(%.1f) ", imgData->data.F32[i][j]);
+        }
+        printf("\n");
+    }
+    printf("Set %d peaks\n", numPeaksOrig);
+
+    //
+    // Call pmCullPeaks() with HUGE maxValue and NULL psRegion.  Should not
+    // remove any peaks.
+    //
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmCullPeaks with large maxValue and NULL psRegion.\n");
+    outData = pmFindImagePeaks(imgData, 0.0);
+    /* XXX: Modify for new pmCullPeaks
+        outData = pmCullPeaks(outData, PS_MAX_F32, NULL);
+
+        if (outData == NULL) {
+            printf("TEST ERROR: pmCullPeaks() returned a non-NULL psList.\n");
+            testStatus = false;
+            return(testStatus);
+        }
+        if (outData->n != numPeaksOrig) {
+            printf("TEST ERROR (0): pmCullPeaks incorrectly removed peaks\n");
+            printf("The pmCullPeaks() output had %d peaks, should have had %d peaks.\n", outData->n, numPeaksOrig);
+            testStatus = false;
+        }
+    */
+    psFree(outData);
+
+    //
+    // Call pmCullPeaks() with TINY maxValue and NULL psRegion.  Should
+    // remove all peaks.
+    //
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmCullPeaks with tiny maxValue and NULL psRegion.\n");
+    outData = pmFindImagePeaks(imgData, 0.0);
+    printf("pmFindImagePeaks found %ld peaks\n", outData->n);
+    /* XXX: Modify for new pmCullPeaks
+        outData = pmCullPeaks(outData, 0.0, NULL);
+
+        if (outData == NULL) {
+            printf("TEST ERROR: pmCullPeaks() returned a non-NULL psList.\n");
+            testStatus = false;
+            return(testStatus);
+        }
+        if (outData->n != 0) {
+            printf("TEST ERROR (1): pmCullPeaks incorrectly removed peaks\n");
+            printf("The pmCullPeaks() output had %d peaks, should have had %d peaks.\n", outData->n, 0);
+            testStatus = false;
+        }
+        psFree(outData);
+    */
+
+    //
+    // Call pmCullPeaks() with HUGE maxValue and disjoint psRegion.  Should
+    // not remove any peaks.
+    //
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmCullPeaks with large maxValue and disjoint psRegion.\n");
+    outData = pmFindImagePeaks(imgData, 0.0);
+    printf("pmFindImagePeaks found %ld peaks\n", outData->n);
+    psRegion tmpRegion = psRegionSet(10000.0, 20000.0, 10000.0, 20000.0);
+
+    /* XXX: Modify for new pmCullPeaks
+        outData = pmCullPeaks(outData, PS_MAX_F32, tmpRegion);
+
+        if (outData == NULL) {
+            printf("TEST ERROR: pmCullPeaks() returned a non-NULL psList.\n");
+            testStatus = false;
+            return(testStatus);
+        }
+        if (outData->n != numPeaksOrig) {
+            printf("TEST ERROR (2): pmCullPeaks incorrectly removed peaks\n");
+            printf("The pmCullPeaks() output had %d peaks, should have had %d peaks.\n", outData->n, numPeaksOrig);
+            testStatus = false;
+        }
+    */
+    psFree(outData);
+
+    //
+    // Call pmCullPeaks() with HUGE maxValue and non-disjoint psRegion.  Should
+    // remove all peaks.
+    //
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmCullPeaks with large maxValue and non-disjoint psRegion.\n");
+    outData = pmFindImagePeaks(imgData, 0.0);
+    printf("pmFindImagePeaks found %ld peaks\n", outData->n);
+    tmpRegion = psRegionSet(-PS_MAX_F32, PS_MAX_F32, -PS_MAX_F32, PS_MAX_F32);
+    /* XXX: Modify for new pmCullPeaks
+        outData = pmCullPeaks(outData, PS_MAX_F32, tmpRegion);
+
+        if (outData == NULL) {
+            printf("TEST ERROR: pmCullPeaks() returned a non-NULL psList.\n");
+            testStatus = false;
+            return(testStatus);
+        }
+        if (outData->n != 0) {
+            printf("TEST ERROR (3): pmCullPeaks incorrectly removed peaks\n");
+            printf("The pmCullPeaks() output had %d peaks, should have had %d peaks.\n", outData->n, 0);
+            testStatus = false;
+        }
+    */
+    psFree(outData);
+
+    printf("----------------------------------------------------------------------------------\n");
+    psFree(imgData);
+    return(testStatus);
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+#define TST04_NUM_ROWS 100
+#define TST04_NUM_COLS 100
+#define TST04_SKY 20.0
+#define TST04_INNER_RADIUS 3
+#define TST04_OUTER_RADIUS 5
+/******************************************************************************
+test04(): We first test pmSourceLocalSky() with various NULL and unallowable
+input parameters.
+ 
+XXX: Should we produce tests with boundary numbers for the inner/outer radius?
+ 
+XXX: Call this with varying sizes for the image.
+ *****************************************************************************/
+int test04( void )
+{
+    bool testStatus = true;
+    psImage *imgData = psImageAlloc(TST04_NUM_COLS, TST04_NUM_ROWS, PS_TYPE_F32);
+    psImageInit(imgData, TST04_SKY);
+    psImage *imgMask = psImageAlloc(TST04_NUM_COLS, TST04_NUM_ROWS, PS_TYPE_U8);
+    psImageInit(imgMask, 0);
+    //    psImage *imgMaskS8 = psImageAlloc(TST04_NUM_COLS, TST04_NUM_ROWS, PS_TYPE_S8);
+    //    psImageInit(imgMaskS8, 0);
+    psImage *imgDataF64 = psImageAlloc(TST04_NUM_COLS, TST04_NUM_ROWS, PS_TYPE_F64);
+    psImageInit(imgDataF64, 0.0);
+    pmPeak *tmpPeak = pmPeakAlloc((psF32) (TST04_NUM_ROWS / 2),
+                                  (psF32) (TST04_NUM_COLS / 2),
+                                  200.0,
+                                  PM_PEAK_LONE);
+    pmSource *tmpSource = pmSourceAlloc();
+    tmpSource->pixels = imgData;
+    tmpSource->mask = imgMask;
+    tmpSource->peak = tmpPeak;
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceLocalSky with NULL tmpSource.  Should generate error and return FALSE.\n");
+    bool rc = pmSourceLocalSky(NULL, PS_STAT_SAMPLE_MEAN, 10.0);
+    if (rc != false) {
+        printf("TEST ERROR: pmSourceLocalSky() returned a non-FALSE pmSource.\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceLocalSky with Radius<0.0.  Should generate error and return FALSE.\n");
+    rc = pmSourceLocalSky(tmpSource, PS_STAT_SAMPLE_MEAN, -10.0);
+    if (rc != false) {
+        printf("TEST ERROR: pmSourceLocalSky() returned a non-FALSE pmSource.\n");
+        testStatus = false;
+    }
+
+    //
+    // XXX: The following code should be a separate function, and we should call it
+    // with a variety of image sizes, peaks.
+    //
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceLocalSky with valid data.\n");
+    tmpPeak->x = (psF32) (TST04_NUM_ROWS / 2);
+    tmpPeak->y = (psF32) (TST04_NUM_COLS / 2);
+    rc = pmSourceLocalSky(tmpSource, PS_STAT_SAMPLE_MEAN, 10.0);
+
+    if (rc == false) {
+        printf("TEST ERROR: pmSourceLocalSky() returned a FALSE pmSource.\n");
+        testStatus = false;
+    } else {
+        if (tmpSource->peak == NULL) {
+            printf("TEST ERROR: pmSourceLocalSky() returned a NULL pmSource->peak.\n");
+            testStatus = false;
+        } else {
+            if (tmpSource->peak->x != tmpPeak->x) {
+                printf("TEST ERROR: pmSourceLocalSky() pmSource->peak->x was %d, should have been %d.\n",
+                       tmpSource->peak->x, tmpPeak->x);
+                testStatus = false;
+            }
+
+            if (tmpSource->peak->y != tmpPeak->y) {
+                printf("TEST ERROR: pmSourceLocalSky() pmSource->peak->y was %d, should have been %d.\n",
+                       tmpSource->peak->y, tmpPeak->y);
+                testStatus = false;
+            }
+
+            if (tmpSource->peak->counts != tmpPeak->counts) {
+                printf("TEST ERROR: pmSourceLocalSky() pmSource->peak->counts was %f, should have been %f.\n",
+                       tmpSource->peak->counts, tmpPeak->counts);
+                testStatus = false;
+            }
+
+            if (tmpSource->peak->class != tmpPeak->class) {
+                printf("TEST ERROR: pmSourceLocalSky() pmSource->peak->class was %d, should have been %d.\n",
+                       tmpSource->peak->class, tmpPeak->class);
+                testStatus = false;
+            }
+        }
+
+        if (tmpSource->moments == NULL) {
+            printf("TEST ERROR: pmSourceLocalSky() returned a NULL pmSource->moments.\n");
+            testStatus = false;
+        } else {
+            if (tmpSource->moments->Sky != TST04_SKY) {
+                printf("TEST ERROR: pmSourceLocalSky() pmSource->moments->Sky was %f, should have been %f.\n", tmpSource->moments->Sky, TST04_SKY);
+                testStatus = false;
+            }
+        }
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    psFree(tmpSource);
+    //    psFree(imgData);
+    //    psFree(imgDataF64);
+    //    psFree(imgMask);
+    //    psFree(imgMaskS8);
+    return(testStatus);
+}
+
+#define TST05_NUM_ROWS 100
+#define TST05_NUM_COLS 100
+#define TST05_SKY 20.0
+#define TST05_INNER_RADIUS 3
+#define TST05_OUTER_RADIUS 5
+/******************************************************************************
+test05(): We first test pmSourceMoments() with various NULL and unallowable
+input parameters.
+ 
+XXX: Should we produce tests with boundary numbers for the inner/outer radius?
+ 
+XXX: Call this with varying sizes for the image.
+ 
+XXX: The actual values of the moments are not tested.
+ *****************************************************************************/
+int test05( void )
+{
+    bool testStatus = true;
+    psImage *imgData = psImageAlloc(TST04_NUM_COLS, TST04_NUM_ROWS, PS_TYPE_F32);
+    psImageInit(imgData, TST04_SKY);
+    psImage *imgMask = psImageAlloc(TST04_NUM_COLS, TST04_NUM_ROWS, PS_TYPE_U8);
+    psImageInit(imgMask, 0);
+    pmPeak *tmpPeak = pmPeakAlloc((psF32) (TST04_NUM_ROWS / 2),
+                                  (psF32) (TST04_NUM_COLS / 2),
+                                  200.0,
+                                  PM_PEAK_LONE);
+    pmSource *tmpSource = pmSourceAlloc();
+    tmpSource->pixels = imgData;
+    tmpSource->mask = imgMask;
+    tmpSource->peak = tmpPeak;
+    psBool rc = pmSourceLocalSky(tmpSource, PS_STAT_SAMPLE_MEAN, 10.0);
+
+    if (rc == false) {
+        printf("TEST ERROR: pmSourceLocalSky() returned a FALSE pmSource.\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceMoments with NULL pmSource.  Should generate error and return FALSE.\n");
+    rc = pmSourceMoments(NULL, 10.0);
+    if (rc != false) {
+        printf("TEST ERROR: pmSourceMoments() returned TRUE.\n");
+        testStatus = false;
+    }
+    // XXX: test with pmSource->peaks NULL
+    // XXX: test with pmSource->pixels NULL
+    // XXX: test with pmSource->mask NULL
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceMoments with radius < 0.0.  Should generate error and return FALSE.\n");
+    rc = pmSourceMoments(tmpSource, -10.0);
+    if (rc != false) {
+        printf("TEST ERROR: pmSourceMoments() returned TRUE.\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    psFree(tmpSource);
+    return(testStatus);
+
+}
+
+#define TST09_NUM_ROWS 70
+#define TST09_NUM_COLS 70
+#define TST09_SKY 5.0
+#define TST09_INNER_RADIUS 3
+#define TST09_OUTER_RADIUS 10
+#define LEVEL (TST09_SKY + 10.0)
+/******************************************************************************
+test09(): We first test pmSourceContour() with various NULL and unallowable
+input parameters.
+ 
+XXX: We don't verify the numbers.
+ *****************************************************************************/
+int test09( void )
+{
+    bool testStatus = true;
+    psImage *imgData = psImageAlloc(TST09_NUM_COLS, TST09_NUM_ROWS, PS_TYPE_F32);
+    psImageInit(imgData, TST09_SKY);
+    psImage *imgMask = psImageAlloc(TST09_NUM_COLS, TST09_NUM_ROWS, PS_TYPE_U8);
+    psImageInit(imgMask, 0);
+    pmPeak *tmpPeak = pmPeakAlloc((psF32) (TST09_NUM_ROWS / 2),
+                                  (psF32) (TST09_NUM_COLS / 2),
+                                  200.0,
+                                  PM_PEAK_LONE);
+    pmSource *tmpSource = pmSourceAlloc();
+    tmpSource->pixels = imgData;
+    tmpSource->mask = imgMask;
+    tmpSource->peak = tmpPeak;
+    psBool rc = pmSourceLocalSky(tmpSource, PS_STAT_SAMPLE_MEAN, 10.0);
+    if (rc == false) {
+        printf("TEST ERROR: pmSourceLocalSky() returned a FALSE pmSource.\n");
+        testStatus = false;
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceContour with NULL pmSource .  Should generate error, return FALSE.\n");
+    rc = pmSourceContour(NULL, imgData, LEVEL, PS_CONTOUR_CRUDE);
+    if (rc != false) {
+        printf("TEST ERROR: pmSourceContour() returned TRUE.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceContour with NULL psImage .  Should generate error, return FALSE.\n");
+    rc = pmSourceContour(tmpSource, NULL, LEVEL, PS_CONTOUR_CRUDE);
+    if (rc != FALSE) {
+        printf("TEST ERROR: pmSourceContour() returned TRUE.\n");
+        testStatus = false;
+        psFree(rc);
+    }
+
+    //
+    // XXX: pmSourceContour() has a problem with contour tops/bottoms.
+    // Must correct this.
+    //
+    if (1) {
+        printf("----------------------------------------------------------------------------------\n");
+        printf("Calling pmSourceContour with acceptable data.\n");
+        printf("NOTE: must figure out the parameters for this test to be meaningful.\n");
+        tmpSource->modelPSF->params->data.F32[0] = TST09_SKY;
+        tmpSource->modelPSF->params->data.F32[1] = 15.0;
+        tmpSource->modelPSF->params->data.F32[2] = (psF32) (TST09_NUM_ROWS / 2);
+        tmpSource->modelPSF->params->data.F32[3] = (psF32) (TST09_NUM_COLS / 2);
+        tmpSource->modelPSF->params->data.F32[4] = 2.0;
+        tmpSource->modelPSF->params->data.F32[5] = 2.0;
+        tmpSource->modelPSF->params->data.F32[6] = 2.0;
+        rc = pmSourceContour(tmpSource, imgData, LEVEL, PS_CONTOUR_CRUDE);
+        if (rc == false) {
+            printf("TEST ERROR: pmSourceContour() returned FALSE.\n");
+            testStatus = false;
+        } else {
+            psFree(rc);
+        }
+    }
+
+    psFree(tmpSource);
+    return(testStatus);
+}
+
+#define TST15_NUM_ROWS 100
+#define TST15_NUM_COLS 100
+#define TST15_SKY 10.0
+#define TST15_INNER_RADIUS 3
+#define TST15_OUTER_RADIUS 5
+/******************************************************************************
+test15(): We first test pmSourceAddModel() with various NULL and unallowable
+input parameters.
+ 
+XXX: We don't verify the numbers.
+ *****************************************************************************/
+/*
+int test15( void )
+{
+    bool testStatus = true;
+    psImage *imgData = psImageAlloc(TST15_NUM_COLS, TST15_NUM_ROWS, PS_TYPE_F32);
+    psImageInit(imgData, TST15_SKY);
+    psImage *imgMask = psImageAlloc(TST15_NUM_COLS, TST15_NUM_ROWS, PS_TYPE_U8);
+    psImageInit(imgMask, 0);
+    pmPeak *tmpPeak = pmPeakAlloc((psF32) (TST15_NUM_ROWS / 2),
+                                  (psF32) (TST15_NUM_COLS / 2),
+                                  200.0,
+                                  PM_PEAK_LONE);
+    pmSource *tmpSource = pmSourceAlloc();
+    tmpSource->pixels = imgData;
+    tmpSource->mask = imgMask;
+    tmpSource->peak = tmpPeak;
+    psBool rc = pmSourceLocalSky(tmpSource, PS_STAT_SAMPLE_MEAN, 10.0);
+    if (rc == false) {
+        printf("TEST ERROR: pmSourceLocalSky() returned a FALSE pmSource.\n");
+        testStatus = false;
+    }
+ 
+ 
+    tmpSource->modelPSF = pmModelAlloc(PS_MODEL_GAUSS);
+    tmpSource->modelPSF->params->data.F32[0] = 5.0;
+    tmpSource->modelPSF->params->data.F32[1] = 70.0;
+    tmpSource->modelPSF->params->data.F32[2] = (psF32) (TST15_NUM_ROWS / 2);
+    tmpSource->modelPSF->params->data.F32[3] = (psF32) (TST15_NUM_COLS / 2);
+    tmpSource->modelPSF->params->data.F32[4] = 1.0;
+    tmpSource->modelPSF->params->data.F32[5] = 1.0;
+    tmpSource->modelPSF->params->data.F32[6] = 2.0;
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceAddModel with NULL psImage.  Should generate error, return FALSE.\n");
+    rc = pmSourceAddModel(NULL, tmpSource, true);
+    if (rc == true) {
+        printf("TEST ERROR: pmSourceAddModel() returned TRUE.\n");
+        testStatus = false;
+    }
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceAddModel with NULL psSrc.  Should generate error, return FALSE.\n");
+    rc = pmSourceAddModel(imgData, NULL, true);
+    if (rc == true) {
+        printf("TEST ERROR: pmSourceAddModel() returned TRUE.\n");
+        testStatus = false;
+    }
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceAddModel with acceptable data.\n");
+    rc = pmSourceAddModel(imgData, tmpSource, true);
+    if (rc != true) {
+        printf("TEST ERROR: pmSourceAddModel() returned FALSE.\n");
+        testStatus = false;
+    }
+ 
+    psFree(tmpSource);
+    psFree(imgData);
+    return(testStatus);
+}
+*/
+
+#define TST16_NUM_ROWS 100
+#define TST16_NUM_COLS 100
+#define TST16_SKY 10.0
+#define TST16_INNER_RADIUS 3
+#define TST16_OUTER_RADIUS 5
+/******************************************************************************
+test16(): We first test pmSourceSubModel() with various NULL and unallowable
+input parameters.
+ 
+XXX: We don't verify the numbers.
+ *****************************************************************************/
+/*
+int test16( void )
+{
+    bool testStatus = true;
+    psImage *imgData = psImageAlloc(TST16_NUM_COLS, TST16_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < imgData->numRows; i++) {
+        for (psS32 j = 0 ; j < imgData->numCols; j++) {
+            imgData->data.F32[i][j] = TST16_SKY;
+        }
+    }
+    pmSource *tmpSource = NULL;
+    psBool rc = false;
+ 
+    pmPeak *tmpPeak = pmPeakAlloc((psF32) (TST16_NUM_ROWS / 2),
+                                  (psF32) (TST16_NUM_COLS / 2),
+                                  200.0,
+                                  PM_PEAK_LONE);
+ 
+    printf("Calling pmSourceLocalSky with valid data.\n");
+    tmpPeak->x = (psF32) (TST16_NUM_ROWS / 2);
+    tmpPeak->y = (psF32) (TST16_NUM_COLS / 2);
+    tmpSource = pmSourceLocalSky(imgData,
+                                 tmpPeak,
+                                 PS_STAT_SAMPLE_MEAN,
+                                 (psF32) TST16_INNER_RADIUS,
+                                 (psF32) TST16_OUTER_RADIUS);
+ 
+    if (tmpSource == NULL) {
+        printf("TEST ERROR: pmSourceLocalSky() returned a NULL pmSource.\n");
+        testStatus = false;
+    }
+ 
+    tmpSource->modelPSF = pmModelAlloc(PS_MODEL_GAUSS);
+    tmpSource->modelPSF->params->data.F32[0] = 5.0;
+    tmpSource->modelPSF->params->data.F32[1] = 70.0;
+    tmpSource->modelPSF->params->data.F32[2] = (psF32) (TST16_NUM_ROWS / 2);
+    tmpSource->modelPSF->params->data.F32[3] = (psF32) (TST16_NUM_COLS / 2);
+    tmpSource->modelPSF->params->data.F32[4] = 1.0;
+    tmpSource->modelPSF->params->data.F32[5] = 1.0;
+    tmpSource->modelPSF->params->data.F32[6] = 2.0;
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceSubModel with NULL psImage.  Should generate error, return FALSE.\n");
+    rc = pmSourceSubModel(NULL, tmpSource, true);
+    if (rc == true) {
+        printf("TEST ERROR: pmSourceSubModel() returned TRUE.\n");
+        testStatus = false;
+    }
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceSubModel with NULL psSrc.  Should generate error, return FALSE.\n");
+    rc = pmSourceSubModel(imgData, NULL, true);
+    if (rc == true) {
+        printf("TEST ERROR: pmSourceSubModel() returned TRUE.\n");
+        testStatus = false;
+    }
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceSubModel with acceptable data.\n");
+    rc = pmSourceSubModel(imgData, tmpSource, true);
+    if (rc != true) {
+        printf("TEST ERROR: pmSourceSubModel() returned FALSE.\n");
+        testStatus = false;
+    }
+ 
+    psFree(tmpSource);
+    psFree(imgData);
+    return(testStatus);
+}
+*/
+
+#define TST20_NUM_ROWS 100
+#define TST20_NUM_COLS 100
+#define TST20_SKY 10.0
+#define TST20_INNER_RADIUS 3
+#define TST20_OUTER_RADIUS 5
+/******************************************************************************
+test20(): We first test pmSourceSubModel() with various NULL and unallowable
+input parameters.
+ 
+XXX: We don't verify the numbers.
+ *****************************************************************************/
+/*
+int test20( void )
+{
+    bool testStatus = true;
+    psImage *imgData = psImageAlloc(TST20_NUM_COLS, TST20_NUM_ROWS, PS_TYPE_F32);
+    for (psS32 i = 0 ; i < imgData->numRows; i++) {
+        for (psS32 j = 0 ; j < imgData->numCols; j++) {
+            imgData->data.F32[i][j] = TST20_SKY;
+        }
+    }
+    pmSource *tmpSource = NULL;
+    psBool rc = false;
+ 
+    pmPeak *tmpPeak = pmPeakAlloc((psF32) (TST20_NUM_ROWS / 2),
+                                  (psF32) (TST20_NUM_COLS / 2),
+                                  200.0,
+                                  PM_PEAK_LONE);
+ 
+    printf("Calling pmSourceLocalSky with valid data.\n");
+    tmpPeak->x = (psF32) (TST20_NUM_ROWS / 2);
+    tmpPeak->y = (psF32) (TST20_NUM_COLS / 2);
+    tmpSource = pmSourceLocalSky(imgData,
+                                 tmpPeak,
+                                 PS_STAT_SAMPLE_MEAN,
+                                 (psF32) TST20_INNER_RADIUS,
+                                 (psF32) TST20_OUTER_RADIUS);
+ 
+    if (tmpSource == NULL) {
+        printf("TEST ERROR: pmSourceLocalSky() returned a NULL pmSource.\n");
+        testStatus = false;
+    }
+ 
+    tmpSource->modelPSF = pmModelAlloc(PS_MODEL_GAUSS);
+ 
+ 
+    tmpSource->modelPSF->params->data.F32[0] = 5.0;
+    tmpSource->modelPSF->params->data.F32[1] = 70.0;
+    tmpSource->modelPSF->params->data.F32[2] = (psF32) (TST20_NUM_ROWS / 2);
+    tmpSource->modelPSF->params->data.F32[3] = (psF32) (TST20_NUM_COLS / 2);
+    tmpSource->modelPSF->params->data.F32[4] = 1.0;
+    tmpSource->modelPSF->params->data.F32[5] = 1.0;
+    tmpSource->modelPSF->params->data.F32[6] = 2.0;
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceFitModel with NULL psImage.  Should generate error, return FALSE.\n");
+    rc = pmSourceFitModel(tmpSource, NULL);
+    if (rc == true) {
+        printf("TEST ERROR: pmSourceFitModel() returned TRUE.\n");
+        testStatus = false;
+    }
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceFitModel with NULL pmSource.  Should generate error, return FALSE.\n");
+    rc = pmSourceFitModel(NULL, imgData);
+    if (rc == true) {
+        printf("TEST ERROR: pmSourceFitModel() returned TRUE.\n");
+        testStatus = false;
+    }
+ 
+    printf("----------------------------------------------------------------------------------\n");
+    printf("Calling pmSourceFitModel with acceptable data.\n");
+    rc = pmSourceFitModel(tmpSource, imgData);
+    printf("pmSourceFitModel returned %d\n", rc);
+ 
+    // XXX: Memory leaks are not being tested
+    psVector *junk = psVectorAlloc(10, PS_TYPE_F32);
+    junk->data.F32[0] = 0.0;
+ 
+    psFree(tmpSource);
+    psFree(imgData);
+    return(testStatus);
+}
+*/
+
Index: /tags/RHL_v1_1/psModules/test/objects/verified/tst_pmObjects01.stderr
===================================================================
--- /tags/RHL_v1_1/psModules/test/objects/verified/tst_pmObjects01.stderr	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/objects/verified/tst_pmObjects01.stderr	(revision 9533)
@@ -0,0 +1,78 @@
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmObjects01.c                                          *
+*            TestPoint: Test Point Driver{pmObjects: structure allocators and deallocators} *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|E|pmModelParameterCount (FILE:LINENO)
+    Undefined pmModelType
+
+---> TESTPOINT PASSED (Test Point Driver{pmObjects: structure allocators and deallocators} | tst_pmObjects01.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmObjects01.c                                          *
+*            TestPoint: Test Point Driver{pmObjects: psFindVectorPeaks()}          *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|E|pmFindVectorPeaks (FILE:LINENO)
+    Unallowable operation: psVector vector or its data is NULL.
+<HOST>|E|pmFindVectorPeaks (FILE:LINENO)
+    Unallowable operation: psVector vector has no elements.
+<HOST>|E|pmFindVectorPeaks (FILE:LINENO)
+    Unallowable operation: psVector vector has incorrect type.
+
+---> TESTPOINT PASSED (Test Point Driver{pmObjects: psFindVectorPeaks()} | tst_pmObjects01.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmObjects01.c                                          *
+*            TestPoint: Test Point Driver{pmObjects: psFindImagePeaks()}           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|E|psImageAlloc (FILE:LINENO)
+    Specified number of rows (0) or columns (0) is invalid.
+<HOST>|E|pmFindImagePeaks (FILE:LINENO)
+    Unallowable operation: psImage image or its data is NULL.
+<HOST>|E|pmFindImagePeaks (FILE:LINENO)
+    Unallowable operation: psImage image or its data is NULL.
+<HOST>|E|pmFindImagePeaks (FILE:LINENO)
+    Unallowable operation: psImage image has incorrect type.
+
+---> TESTPOINT PASSED (Test Point Driver{pmObjects: psFindImagePeaks()} | tst_pmObjects01.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmObjects01.c                                          *
+*            TestPoint: Test Point Driver{pmObjects: pmCullPeaks()}                *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (Test Point Driver{pmObjects: pmCullPeaks()} | tst_pmObjects01.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmObjects01.c                                          *
+*            TestPoint: Test Point Driver{pmObjects: pmSourceLocalSky()}           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|E|pmSourceLocalSky (FILE:LINENO)
+    Unallowable operation: source is NULL.
+<HOST>|E|pmSourceLocalSky (FILE:LINENO)
+    Error: Radius is 0 or less.
+
+---> TESTPOINT PASSED (Test Point Driver{pmObjects: pmSourceLocalSky()} | tst_pmObjects01.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmObjects01.c                                          *
+*            TestPoint: Test Point Driver{pmObjects: pmSourceMoments()}            *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|E|pmSourceMoments (FILE:LINENO)
+    Unallowable operation: source is NULL.
+<HOST>|E|pmSourceMoments (FILE:LINENO)
+    Error: !(radius > 0.0) (-10.000000 0.000000).
+
+---> TESTPOINT PASSED (Test Point Driver{pmObjects: pmSourceMoments()} | tst_pmObjects01.c)
+
Index: /tags/RHL_v1_1/psModules/test/objects/verified/tst_pmObjects01.stdout
===================================================================
--- /tags/RHL_v1_1/psModules/test/objects/verified/tst_pmObjects01.stdout	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/objects/verified/tst_pmObjects01.stdout	(revision 9533)
@@ -0,0 +1,83 @@
+Testing pmPeakAlloc()...
+Testing pmMomentsAlloc()...
+----------------------------------------------------------------------------------
+Calling pmFindVectorPeaks with NULL psVector.  Should generate error and return NULL.
+----------------------------------------------------------------------------------
+Calling pmFindVectorPeaks with empty psVector.  Should generate error and return NULL.
+----------------------------------------------------------------------------------
+Calling pmFindVectorPeaks with PS_TYPE_F64 psVector.  Should generate error and return NULL.
+-------------- Calling test_pmFindVectorPeaks on an 1 size vector. --------------
+Test pmFindVectorPeaks() with a first-element peak.
+Test pmFindVectorPeaks() with a first-element peak, large threshold.
+-------------- Calling test_pmFindVectorPeaks on an 10 size vector. --------------
+Test pmFindVectorPeaks() with a first-element peak.
+Test pmFindVectorPeaks() with a first-element peak, large threshold.
+Test pmFindVectorPeaks() with a last-element peak.
+Test pmFindVectorPeaks() with a last-element peak, large threshold.
+Test pmFindVectorPeaks() with all even-numbered elements peak.
+Test pmFindVectorPeaks() with all even-numbered elements peak, large threshold.
+----------------------------------------------------------------------------------
+Calling pmFindImagePeaks with NULL psImage.  Should generate error and return NULL.
+----------------------------------------------------------------------------------
+Calling pmFindImagePeaks with empty psImage.  Should generate error and return NULL.
+----------------------------------------------------------------------------------
+Calling pmFindImagePeaks with PS_TYPE_F64 psImage.  Should generate error and return NULL.
+----------------------------------------------------------------------------------
+-------------- Calling test_pmFindImagePeaks on an 5-by-5 image. --------------
+(50.0) (5.0) (4.0) (5.0) (50.0) 
+(5.0) (2.0) (1.0) (2.0) (5.0) 
+(4.0) (1.0) (50.0) (1.0) (4.0) 
+(5.0) (2.0) (1.0) (2.0) (5.0) 
+(50.0) (5.0) (4.0) (5.0) (50.0) 
+-------------- Calling test_pmFindImagePeaks on an 10-by-5 image. --------------
+(125.0) (26.0) (25.0) (26.0) (125.0) 
+(20.0) (17.0) (16.0) (17.0) (20.0) 
+(13.0) (10.0) (9.0) (10.0) (13.0) 
+(8.0) (5.0) (4.0) (5.0) (8.0) 
+(5.0) (2.0) (1.0) (2.0) (5.0) 
+(4.0) (1.0) (125.0) (1.0) (4.0) 
+(5.0) (2.0) (1.0) (2.0) (5.0) 
+(8.0) (5.0) (4.0) (5.0) (8.0) 
+(13.0) (10.0) (9.0) (10.0) (13.0) 
+(125.0) (17.0) (16.0) (17.0) (125.0) 
+-------------- Calling test_pmFindImagePeaks on an 5-by-10 image. --------------
+(125.0) (20.0) (13.0) (8.0) (5.0) (4.0) (5.0) (8.0) (13.0) (125.0) 
+(26.0) (17.0) (10.0) (5.0) (2.0) (1.0) (2.0) (5.0) (10.0) (17.0) 
+(25.0) (16.0) (9.0) (4.0) (1.0) (125.0) (1.0) (4.0) (9.0) (16.0) 
+(26.0) (17.0) (10.0) (5.0) (2.0) (1.0) (2.0) (5.0) (10.0) (17.0) 
+(125.0) (20.0) (13.0) (8.0) (5.0) (4.0) (5.0) (8.0) (13.0) (125.0) 
+(10.0) (0.0) (10.0) (0.0) (10.0) (0.0) (10.0) (0.0) (10.0) (0.0) 
+(0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) 
+(12.0) (0.0) (12.0) (0.0) (12.0) (0.0) (12.0) (0.0) (12.0) (0.0) 
+(0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) 
+(14.0) (0.0) (14.0) (0.0) (14.0) (0.0) (14.0) (0.0) (14.0) (0.0) 
+(0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) 
+(16.0) (0.0) (16.0) (0.0) (16.0) (0.0) (16.0) (0.0) (16.0) (0.0) 
+(0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) 
+(18.0) (0.0) (18.0) (0.0) (18.0) (0.0) (18.0) (0.0) (18.0) (0.0) 
+(0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) (0.0) 
+Set 25 peaks
+----------------------------------------------------------------------------------
+Calling pmCullPeaks with large maxValue and NULL psRegion.
+----------------------------------------------------------------------------------
+Calling pmCullPeaks with tiny maxValue and NULL psRegion.
+pmFindImagePeaks found 25 peaks
+----------------------------------------------------------------------------------
+Calling pmCullPeaks with large maxValue and disjoint psRegion.
+pmFindImagePeaks found 25 peaks
+----------------------------------------------------------------------------------
+Calling pmCullPeaks with large maxValue and non-disjoint psRegion.
+pmFindImagePeaks found 25 peaks
+----------------------------------------------------------------------------------
+----------------------------------------------------------------------------------
+Calling pmSourceLocalSky with NULL tmpSource.  Should generate error and return FALSE.
+----------------------------------------------------------------------------------
+Calling pmSourceLocalSky with Radius<0.0.  Should generate error and return FALSE.
+----------------------------------------------------------------------------------
+Calling pmSourceLocalSky with valid data.
+----------------------------------------------------------------------------------
+----------------------------------------------------------------------------------
+Calling pmSourceMoments with NULL pmSource.  Should generate error and return FALSE.
+----------------------------------------------------------------------------------
+Calling pmSourceMoments with radius < 0.0.  Should generate error and return FALSE.
+----------------------------------------------------------------------------------
Index: /tags/RHL_v1_1/psModules/test/pslib/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/pslib/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/pslib/.cvsignore	(revision 9533)
@@ -0,0 +1,5 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
Index: /tags/RHL_v1_1/psModules/test/pslib/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/pslib/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/pslib/Makefile.am	(revision 9533)
@@ -0,0 +1,14 @@
+AM_LDFLAGS = -L$(top_builddir)/src -lpsmodules $(PSMODULES_LIBS)
+AM_CFLAGS  = @AM_CFLAGS@ $(PSMODULES_CFLAGS) $(SRCINC)
+
+TESTS =
+
+check_PROGRAMS = $(TESTS)
+
+TESTS_ENVIRONMENT = perl $(top_srcdir)/test/runTest --verified=$(srcdir)/verified
+
+tests: $(TESTS)
+
+CLEANFILES = $(TESTS) temp/* *~
+
+test: check
Index: /tags/RHL_v1_1/psModules/test/runTest
===================================================================
--- /tags/RHL_v1_1/psModules/test/runTest	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/runTest	(revision 9533)
@@ -0,0 +1,400 @@
+#!/usr/bin/perl
+#
+#  This is a perl script to execute a single test driver program.  The script
+#  will examine the return value of the test driver and capture STDOUT, STDERR
+#  to files.  If the return value of the test driver is not zero, the test is
+#  deemed a failure.  STDOUT and STDERR files will be place in a subdirectory
+#  named temp and the files will be compared with verified STDOUT, STDERR files
+#  in a subdirectory named verified.  If there are no STDOUT, STDERR files in
+#  the verified directory, the captured STDOUT, STDERR files will be scanned
+#  for words which indicate a failure.
+#
+#  Assumptions:  A verified subdirectory with STDOUT, STDERR files exists for
+#                the test driver specified in the arguements if an exact
+#                match of the streams STDOUT, STDERR is necessary.
+#
+#  Return values:
+#                 Bit mapped values
+#                 0    Test run successfull and all tests passed
+#                 1    Verified directory did not exist
+#                 8    STDOUT files did not compare
+#                16    STDERR files did not compare
+#                32    Test driver doesn't exist or is not executable
+#                64    Test driver did not return zero
+#
+#  SYNOSIS:  runTest testDriverName
+#
+#  $Revison:  $  $Name: not supported by cvs2svn $
+#  $Date: 2005-09-13 20:26:33 $
+#
+#  Copyright 2004 Maui High Performance Computering Center, University of Hawaii
+#
+################################################################################
+
+# Provides functions for handling psS64 command line options
+use Getopt::Long;
+
+$verifiedDir = "verified";
+
+# Assign variables based on the presence of command line options to the script
+GetOptions(
+    "reset!"         => \$reset,
+    "resetStderr!"   => \$resetStderr,
+    "resetStdout!"   => \$resetStdout,
+    "verified=s"     => \$verifiedDir,
+    "help!"          => \$help,
+    "quiet!"         => \$quiet,
+    "printpassfail!" => \$verbose,
+    "printdiff!"     => \$printdiff
+);
+
+if ($help || $#ARGV < 0) {
+    print "\nUsage: runTest  [--help] [--quiet] [--resetStderr] [--resetStdout] [--reset] \\\n",
+          "                [--verified=DIR] [--printpassfail] [--printdiff] testfile(s)\n\n",
+          "Options include:\n",
+          "    --help           Prints this help message\n",
+          "    --quiet          Suppresses all messages\n",
+          "    --resetStderr    Resets the STDERR expected output file in the verified\n",
+          "                     directory\n",
+          "    --resetStdout    Resets the STDOUT expected output file in the verified\n",
+          "                     directory\n",
+          "    --reset          Equivalent to --resetStderr plus --resetStdout\n",
+          "    --verified=DIR   Specifies the location of the verified directory\n",
+          "    --printpassfail  Prints a PASS or FAIL message for each test in each file\n",
+          "    --printdiff      Prints any differences found in the test output and\n",
+          "                     verified expected output.\n\n";
+    exit(0);
+}
+
+if ($reset) {
+    $resetStderr = 1;
+    $resetStdout = 1;
+}
+
+# Initialize exit value
+$exitValue = 0;
+
+# Set up the PSLIB_ROOT environment variable if the user doesn't have it set
+if ( $ENV{'PSLIB_ROOT'} ) {
+
+	# Add PSLIB_ROOT/lib to LD_LIBRARY_PATH and DYLD_LIBRARY_PATH environment vars
+	$ENV{'LD_LIBRARY_PATH'}   = "$ENV{'PSLIB_ROOT'}/src/.libs:$ENV{'LD_LIBRARY_PATH'}";
+	$ENV{'DYLD_LIBRARY_PATH'} = "$ENV{'PSLIB_ROOT'}/src/.libs:$ENV{'DYLD_LIBRARY_PATH'}";
+}
+
+# Loop through the arguements passed to the script
+foreach (@ARGV) {
+    chomp;
+    $testFile = $_;
+
+    # Check if a temp directory already exists in the current work directory
+    if ( !( -e "temp" ) ) {
+        # Create temp directory to store STDOUT, STDERR files during test run
+        `mkdir temp`;
+    }
+
+    # Check if a verified directory exists in the current work directory
+    if ( !( -e $verifiedDir ) ) {
+
+        # Display message that verified subdirectory doesn't exist
+        print STDERR "        Verified directory doesn't exist.\n";
+
+        # Exit script since the test cannot be run with verified files
+        $exitValue = 1;
+    }
+
+    # Check if the test driver file exists and is executable
+    if ( ( -e $testFile ) && ( -x $testFile ) ) {
+
+        # Invoke the test driver
+        system("./$testFile 1> temp/$testFile.stdout 2> temp/$testFile.stderr");
+
+        # Check the return value of the test driver.  A value other than 0
+        # indicates failure of the test driver unless the test driver is name
+        # with a leading 'a' which indicates the test driver is expecting an
+        # abort condition to terminate the driver.
+        if (   ( $? != 0 && ( $testFile !~ /^A/i ) )
+            || ( $? == 0 && ( $testFile =~ /^A/i ) ) )
+        {
+
+            # Display failure message with return value to user
+            if ( $? != 0 && ( $testFile !~ /^A/i ) ) {
+                print STDERR "Failed - Test Driver returned $?, expected 0.\n";
+            }
+            elsif ( $? == 0 && ( $testFile =~ /^A/i ) ) {
+                print STDERR "Failed - Test Driver returned $?, expected abort.\n";
+            }
+            $exitValue |= 64;
+        }
+
+        # Test driver succeeded.
+
+        # Create filtered version of stdout and stderr
+
+        # Open the STDOUT file for reading
+        open( OUTFILE, "< temp/$testFile.stdout" );
+
+        # Open mod file to place filtered STDOUT
+        open( MODFILE,  "> temp/$testFile.stdout.mod" );
+        open( MODFILE2, "> $verifiedDir/$testFile.stdout" ) if $resetStdout;
+
+        # Replace the variable date, time and host information with constants
+        $hostname = `hostname`;
+        chop $hostname;
+        while (<OUTFILE>) {
+            s/\s+\d+:\d+:\d+\w/<TIME>/g;
+            s/\d+:\d+:\d+/<DATE>/g;
+            s/$hostname\s*/<HOST>/g;
+            s/: Line \d+/: Line <LINENO>/g;
+            s/\(.*\:\d+\)/\(FILE\:LINENO\)/g;
+            s/\s+[\_\-\/\.\w]+\:\d+/ FILE\:LINENO/g;
+            s/allocate \d+ bytes at/allocate <N> bytes at/g;
+            s/v\d+.\d+.\d+/vX.X.X/g;
+            s/'xxx' \(\d+\)/'xxx' \(NUM\)/;
+            if (m/TestPoint:\s*([^\*]+)/) {
+                $testfile = $1;
+            }
+            if (m/^---> TESTPOINT\s(\S+)/) {
+                print "\t$testfile- $1\n" if ($verbose || $1 eq "FAILED");
+                if ($1 eq "FAILED") {
+                    print $testoutput;
+                }
+            }
+
+            # Filter lines with malloc.  This is an artifact of memory testing
+            # with the Mac testbed
+            if ( !m/\*\*\*\smalloc/ ) {
+                print MODFILE ($_);
+                print MODFILE2 ($_) if $resetStdout;
+            }
+        }
+
+        # Close mod file
+        close(MODFILE);
+        close(MODFILE2) if $resetStdout;
+
+        # Close STDERR file
+        close(OUTFILE);
+
+        # Open the STDERR file for reading
+        open( OUTFILE, "< temp/$testFile.stderr" );
+
+        # Open mod file to place filtered STDERR
+        open( MODFILE,  "> temp/$testFile.stderr.mod" );
+        open( MODFILE2, "> $verifiedDir/$testFile.stderr" ) if $resetStderr;
+
+        # Replace the variable date, time and host information with constants
+        while (<OUTFILE>) {
+            s/\s+\d+:\d+:\d+\w/<TIME>/g;
+            s/\d+:\d+:\d+/<DATE>/g;
+            s/$hostname\s*/<HOST>/g;
+            s/: Line \d+/: Line <LINENO>/g;
+            s/\(.*\:\d+\)/\(FILE\:LINENO\)/g;
+            s/\s+[\_\-\/\.\w]+\:\d+/ FILE\:LINENO/g;
+            s/allocate \d+ bytes at/allocate <N> bytes at/g;
+            s/v\d+.\d+.\d+/vX.X.X/g;
+            s/'xxx' \(\d+\)/'xxx' \(NUM\)/;
+
+            if (m/\*\*\*\*\*\* TESTPOINT \*\*\*\*\*\*/) {
+                $testoutput = $_;
+            }
+            $testoutput += $_;
+
+            if (m/ TestPoint:\s*([^\*]+)/) {
+                $testfile = $1;
+            }
+            if (m/^---> TESTPOINT\s(\S+)/) {
+                print "\t$testfile- $1\n" if ($verbose || $1 eq "FAILED");
+                if ($1 eq "FAILED") {
+                    print $testoutput;
+                }
+            }
+
+            # Filter lines with malloc.  This is an artifact of memory testing
+            # with the Mac testbed
+
+            if ( !m/\*\*\*\smalloc/ ) {
+                print MODFILE ($_);
+                print MODFILE2 ($_) if $resetStderr;
+            }
+        }
+
+        # Close mod file
+        close(MODFILE);
+        close(MODFILE2) if $resetStderr;
+
+        # Close STDERR file
+        close(OUTFILE);
+
+        # Compare STDOUT capture with verified file
+        $exitValue = &compareStream("$verifiedDir/$testFile.stdout");
+
+        # Check exit value to determine if verified file doesn't exist
+        if ( $exitValue & 2 ) {
+
+            # STDOUT verified doesn't exist.  Search STDOUT capture
+            # for strings indicating error or failure
+            $exitValue |= &errorStrSearch("$testFile.stdout");
+        }
+
+        # Compare STDERR capture with verified file
+        $exitValue |= &compareStream("$verifiedDir/$testFile.stderr");
+
+        # Check exit value to determine if verified file doesn't exist
+        if ( $exitValue & 4 ) {
+
+            # STDERR verified doesn't exist.  Search STDERR capture
+            # for strings indicating error or failure
+            $exitValue |= &errorStrSearch("$testFile.stderr");
+        }
+   }
+    else {
+
+        # Since test driver doesn't exist or is not executable then display
+        # message to user.
+        print STDERR "\tNeed to specify an executable test file.\n";
+
+        # Exit value set to indicate test driver doesn't exist or not executable
+        $exitValue |= 32;
+    }
+}
+
+# Exit for the script with exit value
+# Ignore the first three bit flags in exitValue since there are not indicators
+# of a failed test
+if ( ( $exitValue >> 3 ) != 0 ) {
+    print STDERR "Test failed - return status = $exitValue\n\n";
+}
+else {
+    exit(0);
+}
+
+exit($exitValue);
+
+################################################################################
+#
+#  SUBROUTINE: errorStrSearch
+#
+#      Description:  This subroutine will search the file specified in its
+#                    parameter for error strings and if they do exists
+#                    the appropriate value will be returned.
+#
+#      Parameter(s): fileName - input file to search for strings
+#
+#      Return:   0  -  No error strings found
+#               64  -  Error strings found in STDOUT
+#              128  -  Error strings found in STDERR
+#
+###############################################################################
+
+sub errorStrSearch {
+    local ($fileName)  = @_;
+    local ($returnVal) = 0;
+
+    # Open the captured file
+    open( CAPT_FILE, "<temp/$fileName" );
+
+    # Scan through all the lines of the file searching for error strings
+    while (<CAPT_FILE>) {
+        if (   m/FAIL/i
+            || m/FAULT/i
+            || m/ERROR/i
+            || m/Not Found/i
+            || m/SIGNAL/i
+            || m/NO SUCH FILE/i
+            || m/\|E\|/i
+            || m/\|A\|/i )
+        {
+            print STDERR "\tFailed - File $fileName contains error strings.\n";
+            if ( $fileName =~ m/out/ ) {
+                $returnVal = 64;
+            }
+            elsif ( $fileName =~ m/err/ ) {
+                $returnVal = 128;
+            }
+            last;
+        }
+    }
+
+    # Close the capture file
+    close(CAPT_FILE);
+
+    # Return the return value
+    return ($returnVal);
+}
+
+################################################################################
+#
+#  SUBROUTINE: compareStream
+#
+#      Description:  This subroutine will compare the captured stream file with
+#                    a file in the verified directory if one exists.
+#
+#      Parameter(s): streamFile - input file to compare
+#
+#      Return:   0  -  Compare successful
+#                2  -  STDOUT verified file doesn't exist
+#                4  -  STDERR verified file doesn't exist
+#                8  -  STDOUT verified file doesn't compare
+#               16  -  STDERR verified file doesn't compare
+#
+###############################################################################
+
+sub compareStream {
+    local ($streamFile) = @_;
+    local ($returnVal)  = 0;
+    local ($tempFile)   = "";
+
+    # Check for existence of verified STD stream files
+    if ( !( -e $streamFile ) ) {
+
+        # Set exit value bit 1 to indicate proper failure
+        if ( $streamFile =~ /out$/ ) {
+            $returnVal |= 2;
+        }
+        elsif ( $streamFile =~ /err$/ ) {
+            $returnVal |= 4;
+        }
+    }
+    else {
+
+        # Verified STD stream file exists
+
+        # Create name of the temp file to compare
+        $tempFile = $streamFile;
+        $tempFile =~ s/$verifiedDir/temp/;
+        $tempFile = $tempFile . ".mod";
+
+        # Perform difference on the STD stream files
+        $diffstdout = `diff -b -y --suppress-common-lines $streamFile $tempFile`;
+
+        # Check the return value of the difference
+        if ( $? != 0 ) {
+
+            # Difference of STD stream files failed
+
+            # Check for STDOUT in file name to convey appropirate return value
+            if ( $streamFile =~ /out$/ ) {
+
+                # Display message of the failure of difference to user
+                print STDERR "\tFailed - STDOUT differences\n";
+                print STDERR $diffstdout if $printdiff;
+                # Exit value to indicate STDOUT did not compare
+                $returnVal |= 8;
+            }
+            elsif ( $streamFile =~ /err$/ ) {
+
+                # Display message of the failure of difference to user
+                print STDERR "\tFailed - STDERR differences\n";
+                print STDERR $diffstdout if $printdiff;
+
+                # Exit value to indicate STDERR did not compare
+                $returnVal |= 16;
+            }
+        }
+    }
+
+    # Return the result of the compare
+    return ($returnVal);
+}
+
Index: /tags/RHL_v1_1/psModules/test/tap/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/.cvsignore	(revision 9533)
@@ -0,0 +1,13 @@
+.in
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+config.log
+config.status
+configure
+libtool
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/INSTALL
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/INSTALL	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/INSTALL	(revision 9533)
@@ -0,0 +1,8 @@
+Quick Installation
+
+    ./configure
+    make
+    make check
+    make install
+
+Run "configure --help" for additional options.
Index: /tags/RHL_v1_1/psModules/test/tap/LICENSE
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/LICENSE	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/LICENSE	(revision 9533)
@@ -0,0 +1,23 @@
+Copyright (c) 2004 Nik Clayton
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
Index: /tags/RHL_v1_1/psModules/test/tap/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/Makefile.am	(revision 9533)
@@ -0,0 +1,5 @@
+SUBDIRS  = src
+#SUBDIRS += tests
+
+prove:
+	prove -v -r
Index: /tags/RHL_v1_1/psModules/test/tap/README
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/README	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/README	(revision 9533)
@@ -0,0 +1,11 @@
+NAME
+     tap -- write tests that implement the Test Anything Protocol
+
+SYNOPSIS
+     #include <tap.h>
+
+DESCRIPTION
+     The tap library provides functions for writing test scripts that produce
+     output consistent with the Test Anything Protocol.  A test harness that
+     parses this protocol can run these tests and produce useful reports indi-
+     cating their success or failure.
Index: /tags/RHL_v1_1/psModules/test/tap/bootstrap.sh
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/bootstrap.sh	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/bootstrap.sh	(revision 9533)
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+set -x
+aclocal19 -I /usr/local/share/aclocal || aclocal || exit 1
+autoheader259 || autoheader || exit 1
+libtoolize15 -c -f || libtoolize -c -f || glibtoolize -c -f || exit 1
+automake19 -a -c || automake -a -c || exit 1
+autoconf259 || autoconf || exit 1
Index: /tags/RHL_v1_1/psModules/test/tap/compile
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/compile	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/compile	(revision 9533)
@@ -0,0 +1,142 @@
+#! /bin/sh
+# Wrapper for compilers which do not understand `-c -o'.
+
+scriptversion=2004-10-12.08
+
+# Copyright (C) 1999, 2000, 2003, 2004 Free Software Foundation, Inc.
+# Written by Tom Tromey <tromey@cygnus.com>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+# As a special exception to the GNU General Public License, if you
+# distribute this file as part of a program that contains a
+# configuration script generated by Autoconf, you may include it under
+# the same distribution terms that you use for the rest of that program.
+
+# This file is maintained in Automake, please report
+# bugs to <bug-automake@gnu.org> or send patches to
+# <automake-patches@gnu.org>.
+
+case $1 in
+  '')
+     echo "$0: No command.  Try \`$0 --help' for more information." 1>&2
+     exit 1;
+     ;;
+  -h | --h*)
+    cat <<\EOF
+Usage: compile [--help] [--version] PROGRAM [ARGS]
+
+Wrapper for compilers which do not understand `-c -o'.
+Remove `-o dest.o' from ARGS, run PROGRAM with the remaining
+arguments, and rename the output as expected.
+
+If you are trying to build a whole package this is not the
+right script to run: please start by reading the file `INSTALL'.
+
+Report bugs to <bug-automake@gnu.org>.
+EOF
+    exit 0
+    ;;
+  -v | --v*)
+    echo "compile $scriptversion"
+    exit 0
+    ;;
+esac
+
+ofile=
+cfile=
+eat=
+
+for arg
+do
+  if test -n "$eat"; then
+    eat=
+  else
+    case $1 in
+      -o)
+	# configure might choose to run compile as `compile cc -o foo foo.c'.
+	# So we strip `-o arg' only if arg is an object.
+	eat=1
+	case $2 in
+	  *.o | *.obj)
+	    ofile=$2
+	    ;;
+	  *)
+	    set x "$@" -o "$2"
+	    shift
+	    ;;
+	esac
+	;;
+      *.c)
+	cfile=$1
+	set x "$@" "$1"
+	shift
+	;;
+      *)
+	set x "$@" "$1"
+	shift
+	;;
+    esac
+  fi
+  shift
+done
+
+if test -z "$ofile" || test -z "$cfile"; then
+  # If no `-o' option was seen then we might have been invoked from a
+  # pattern rule where we don't need one.  That is ok -- this is a
+  # normal compilation that the losing compiler can handle.  If no
+  # `.c' file was seen then we are probably linking.  That is also
+  # ok.
+  exec "$@"
+fi
+
+# Name of file we expect compiler to create.
+cofile=`echo "$cfile" | sed -e 's|^.*/||' -e 's/\.c$/.o/'`
+
+# Create the lock directory.
+# Note: use `[/.-]' here to ensure that we don't use the same name
+# that we are using for the .o file.  Also, base the name on the expected
+# object file name, since that is what matters with a parallel build.
+lockdir=`echo "$cofile" | sed -e 's|[/.-]|_|g'`.d
+while true; do
+  if mkdir "$lockdir" >/dev/null 2>&1; then
+    break
+  fi
+  sleep 1
+done
+# FIXME: race condition here if user kills between mkdir and trap.
+trap "rmdir '$lockdir'; exit 1" 1 2 15
+
+# Run the compile.
+"$@"
+ret=$?
+
+if test -f "$cofile"; then
+  mv "$cofile" "$ofile"
+elif test -f "${cofile}bj"; then
+  mv "${cofile}bj" "$ofile"
+fi
+
+rmdir "$lockdir"
+exit $ret
+
+# Local Variables:
+# mode: shell-script
+# sh-indentation: 2
+# eval: (add-hook 'write-file-hooks 'time-stamp)
+# time-stamp-start: "scriptversion="
+# time-stamp-format: "%:y-%02m-%02d.%02H"
+# time-stamp-end: "$"
+# End:
Index: /tags/RHL_v1_1/psModules/test/tap/configure.in
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/configure.in	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/configure.in	(revision 9533)
@@ -0,0 +1,61 @@
+AC_INIT(tap, 1.01)
+AC_CONFIG_SRCDIR(src/tap.c)
+AM_INIT_AUTOMAKE([foreign])
+AC_CONFIG_HEADERS([src/config.h])
+AC_PROG_CC
+AC_PROG_LIBTOOL
+AC_PROG_INSTALL
+
+# Checks for libraries
+case "$host" in
+	*-*-*freebsd4*)
+		LDFLAGS="$LDFLAGS -pthread"
+		HAVE_LIBPTHREAD=1
+		;;
+	*)
+		AC_CHECK_LIB(pthread, main)
+		;;
+esac
+
+dnl build tests at the same time as the source code
+AC_ARG_ENABLE(tests,
+  [AS_HELP_STRING(--enable-tests,build tests at same time as source)],
+  [AC_MSG_RESULT(test building enabled)
+   tests=true],
+   [tests=false])
+AM_CONDITIONAL(BUILD_TESTS, test x$tests = xtrue)
+
+# Checks for header files
+AC_HEADER_STDC
+AC_CHECK_HEADERS([stdlib.h])
+AC_CHECK_HEADERS([pthread.h])
+
+# Checks for  typedefs, structures, and compiler characteristics.
+AC_C_CONST
+
+# Checks for library functions.
+AC_FUNC_VPRINTF
+AC_CHECK_FUNCS([atexit])
+
+AC_CONFIG_FILES([Makefile
+		 src/Makefile
+		 tests/Makefile
+		 tests/diag/Makefile
+		 tests/fail/Makefile
+		 tests/ok/Makefile
+		 tests/ok/ok-hash/Makefile
+		 tests/ok/ok-numeric/Makefile
+		 tests/ok/ok/Makefile
+		 tests/pass/Makefile
+		 tests/plan/Makefile
+		 tests/plan/no-tests/Makefile
+		 tests/plan/no_plan/Makefile
+		 tests/plan/not-enough-tests/Makefile
+		 tests/plan/sane/Makefile
+		 tests/plan/skip_all/Makefile
+		 tests/plan/too-many-plans/Makefile
+		 tests/plan/too-many-tests/Makefile
+		 tests/skip/Makefile
+		 tests/todo/Makefile
+		])
+AC_OUTPUT
Index: /tags/RHL_v1_1/psModules/test/tap/src/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/src/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/src/.cvsignore	(revision 9533)
@@ -0,0 +1,13 @@
+.deps
+.libs
+Makefile
+Makefile.in
+libtap.la
+tap.lo
+config.h
+config.h.in
+stamp-h1
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/src/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/src/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/src/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+TEST_LTLIBS = libtap.la
+libtap_la_SOURCES = tap.c tap.h
+noinst_HEADERS = tap.h
+
+if BUILD_TESTS
+noinst_LTLIBRARIES = $(TEST_LTLIBS)
+else
+check_LTLIBRARIES = $(TEST_LTLIBS)
+endif
+
+#man_MANS = tap.3
+EXTRA_DIST = $(man_MANS)
+
Index: /tags/RHL_v1_1/psModules/test/tap/src/tap.3
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/src/tap.3	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/src/tap.3	(revision 9533)
@@ -0,0 +1,380 @@
+.Dd December 20, 2004
+.Os
+.Dt TAP 3
+.Sh NAME
+.Nm tap
+.Nd write tests that implement the Test Anything Protocol
+.Sh SYNOPSIS
+.In tap.h
+.Sh DESCRIPTION
+The
+.Nm
+library provides functions for writing test scripts that produce output
+consistent with the Test Anything Protocol.  A test harness that parses
+this protocol can run these tests and produce useful reports indicating
+their success or failure.
+.Ss PRINTF STRINGS
+In the descriptions that follow, for any function that takes as the
+last two parameters
+.Dq Fa char * , Fa ...
+it can be assumed that the
+.Fa char *
+is a
+.Fn printf
+-like format string, and the optional arguments are values to be placed
+in that string.
+.Ss TEST PLANS
+.Bl -tag -width indent
+.It Xo
+.Ft int
+.Fn plan_tests "unsigned int"
+.Xc
+.It Xo
+.Ft int
+.Fn plan_no_plan "void"
+.Xc
+.It Xo
+.Ft int
+.Fn plan_skip_all "char *" "..."
+.Xc
+.El
+.Pp
+You must first specify a test plan.  This indicates how many tests you
+intend to run, and allows the test harness to notice if any tests were
+missed, or if the test program exited prematurely.
+.Pp
+To do this, use
+.Fn plan_tests ,
+which returns the number of planned tests.  The function will cause
+your program to exit prematurely if you specify 0 tests.
+.Pp
+In some situations you may not know how many tests you will be running, or
+you are developing your test program, and do not want to update the
+.Fn plan_tests
+parameter every time you make a change.  For those situations use
+.Fn plan_no_plan .
+It returns 1, and indicates to the test harness that an indeterminate number
+of tests will be run.
+.Pp
+Both
+.Fn plan_tests
+and
+.Fn plan_no_plan
+will cause your test program to exit prematurely with a diagnostic
+message if they are called more than once.
+.Pp
+If your test program detects at run time that some required functionality
+is missing (for example, it relies on a database connection which is not
+present, or a particular configuration option that has not been included
+in the running kernel) use
+.Fn plan_skip_all ,
+passing as parameters a string to display indicating the reason for skipping
+the tests.
+.Ss SIMPLE TESTS
+.Bl -tag -width indent
+.It Xo
+.Ft unsigned int
+.Fn ok "expression" "char *" "..."
+.Xc
+.It Xo
+.Ft unsigned int
+.Fn ok1 "expression"
+.Xc
+.It Xo
+.Ft unsigned int
+.Fn pass "char *" "..."
+.Xc
+.It Xo
+.Ft unsigned int
+.Fn fail "char *" "..."
+.Xc
+.El
+.Pp
+Tests are implemented as expressions checked by calls to the
+.Fn ok
+and
+.Fn ok1
+macros.  In both cases
+.Fa expression
+should evaluate to true if the test succeeded.
+.Pp
+.Fn ok
+allows you to specify a name, or comment, describing the test which will
+be included in the output.
+.Fn ok1
+is for those times when the expression to be tested is self
+explanatory and does not need an associated comment.  In those cases
+the test expression becomes the comment.
+.Pp
+These four calls are equivalent:
+.Bd -literal -offset indent
+int i = 5;
+
+ok(i == 5, "i equals 5");      /* Overly verbose */
+ok(i == 5, "i equals %d", i);  /* Just to demonstrate printf-like
+                                  behaviour of the test name */
+ok(i == 5, "i == 5");          /* Needless repetition */
+ok1(i == 5);                   /* Just right */
+.Ed
+.Pp
+It is good practice to ensure that the test name describes the meaning
+behind the test rather than what you are testing.  Viz
+.Bd -literal -offset indent
+ok(db != NULL, "db is not NULL");            /* Not bad, but */
+ok(db != NULL, "Database conn. succeeded");  /* this is better */
+.Ed
+.Pp
+.Fn ok
+and
+.Fn ok1
+return 1 if the expression evaluated to true, and 0 if it evaluated to
+false.  This lets you chain calls from
+.Fn ok
+to
+.Fn diag
+to only produce diagnostic output if the test failed.  For example, this
+code will include diagnostic information about why the database connection
+failed, but only if the test failed.
+.Bd -literal -offset indent
+ok(db != NULL, "Database conn. succeeded") ||
+    diag("Database error code: %d", dberrno);
+.Ed
+.Pp
+You also have
+.Fn pass
+and
+.Fn fail .
+From the Test::More documentation:
+.Bd -literal -offset indent
+Sometimes you just want to say that the tests have passed.
+Usually the case is you've got some complicated condition
+that is difficult to wedge into an ok().  In this case,
+you can simply use pass() (to declare the test ok) or fail
+(for not ok).
+
+Use these very, very, very sparingly.
+.Ed
+.Pp
+These are synonyms for ok(1, ...) and ok(0, ...).
+.Ss SKIPPING TESTS
+.Bl -tag -width indent
+.It Xo
+.Ft int
+.Fn skip "unsigned int" "char *" "..."
+.Xc
+.It Xo
+.Fn skip_start "expression" "unsigned int" "char *" "..."
+.Xc
+.It Xo
+.Fn skip_end
+.Xc
+.El
+.Pp
+Sets of tests can be skipped.  Ordinarily you would do this because
+the test can't be run in this particular testing environment.
+.Pp
+For example, suppose some tests should be run as root.  If the test is
+not being run as root then the tests should be skipped.  In this 
+implementation, skipped tests are flagged as being ok, with a special
+message indicating that they were skipped.  It is your responsibility
+to ensure that the number of tests skipped (the first parameter to
+.Fn skip )
+is correct for the number of tests to skip.
+.Pp
+One way of implementing this is with a
+.Dq do { } while(0);
+loop, or an
+.Dq if( ) { } else { }
+construct, to ensure that there are no additional side effects from the
+skipped tests.
+.Bd -literal -offset indent
+if(getuid() != 0) {
+        skip(1, "because test only works as root");
+} else {
+        ok(do_something_as_root() == 0, "Did something as root");
+}
+.Ed
+.Pp
+Two macros are provided to assist with this.  The previous example could
+be re-written as follows.
+.Bd -literal -offset indent
+skip_start(getuid() != 0, 1, "because test only works as root");
+
+ok(do_something_as_root() == 0, "Did something as root");
+
+skip_end();
+.Ed
+.Ss MARKING TESTS AS Dq TODO
+.Bl -tag -width indent
+.It Xo
+.Ft void
+.Fn todo_start "char *" "..."
+.Xc
+.It Xo
+.Ft void
+.Fn todo_end "void"
+.Xc
+.El
+.Pp
+Sets of tests can be flagged as being
+.Dq TODO .
+These are tests that you expect to fail, probably because you haven't
+fixed a bug, or finished a new feature yet.  These tests will still be
+run, but with additional output that indicates that they are expected
+to fail.  Should a test start to succeed unexpectedly, tools like
+.Xr prove 1
+will indicate this, and you can move the test out of the todo
+block.  This is much more useful than simply commenting out (or
+.Dq #ifdef 0 ... #endif )
+the tests.
+.Bd -literal -offset indent
+todo_start("dwim() not returning true yet");
+
+ok(dwim(), "Did what the user wanted");
+
+todo_end();
+.Ed
+.Pp
+Should
+.Fn dwim
+ever start succeeding you will know about it as soon as you run the
+tests.  Note that
+.Em unlike
+the
+.Fn skip_*
+family, additional code between
+.Fn todo_start
+and
+.Fn todo_end
+.Em is
+executed.
+.Ss SKIP vs. TODO
+From the Test::More documentation;
+.Bd -literal -offset indent
+If it's something the user might not be able to do, use SKIP.
+This includes optional modules that aren't installed, running
+under an OS that doesn't have some feature (like fork() or
+symlinks), or maybe you need an Internet connection and one
+isn't available.
+
+If it's something the programmer hasn't done yet, use TODO.
+This is for any code you haven't written yet, or bugs you have
+yet to fix, but want to put tests in your testing script 
+(always a good idea).
+.Ed
+.Ss DIAGNOSTIC OUTPUT
+.Bl -tag -width indent
+.It Xo
+.Fr unsigned int
+.Fn diag "char *" "..."
+.Xc
+.El
+.Pp
+If your tests need to produce diagnostic output, use
+.Fn diag .
+It ensures that the output will not be considered by the TAP test harness.
+.Fn diag
+adds the necessary trailing
+.Dq \en
+for you.
+.Bd -literal -offset indent
+diag("Expected return code 0, got return code %d", rcode);
+.Ed
+.Pp
+.Fn diag
+always returns 0.
+.Ss EXIT STATUS
+.Bl -tag -width indent
+.It Xo
+.Fr int
+.Fn exit_status void
+.Xc
+.El
+.Pp
+For maximum compatability your test program should return a particular
+exit code.  This is calculated by
+.Fn exit_status
+so it is sufficient to always return from
+.Fn main
+with either
+.Dq return exit_status();
+or
+.Dq exit(exit_status());
+as appropriate.
+.Sh ENVIRONMENT
+The following environment variables affect
+.Nm .
+.Bl -tag -width indent
+.It Ev HARNESS_ACTIVE
+Causes an extra
+.Dq \en
+to be printed before any diagnostic failure output generated by
+.Nm .
+This variable is normally set if tests are being run under Perl's
+Test::Harness.
+.El
+.Sh EXAMPLES
+The
+.Pa tests
+directory in the source distribution contains numerous tests of
+.Nm
+functionality, written using
+.Nm .
+Examine them for examples of how to construct test suites.
+.Sh COMPATABILITY
+.Nm
+strives to be compatible with the Perl Test::More and Test::Harness 
+modules.  The test suite verifies that
+.Nm
+is bug-for-bug compatible with their behaviour.  This is why some
+functions which would more naturally return nothing return constant
+values.
+.Pp
+If the
+.Lb libpthread
+is found at compile time,
+.Nm
+.Em should
+be thread safe.  Indications to the contrary (and test cases that expose
+incorrect behaviour) are very welcome.
+.Sh SEE ALSO
+.Xr Test::More 1 ,
+.Xr Test::Harness 1 ,
+.Xr prove 1
+.Sh STANDARDS
+.Nm
+requires a
+.St -isoC-99
+compiler.  Some of the
+.Nm
+functionality is implemented as variadic macros, and that functionality
+was not formally codified until C99.  Patches to use
+.Nm
+with earlier compilers that have their own implementation of variadic
+macros will be gratefully received.
+.Sh HISTORY
+.Nm
+was written to help improve the quality and coverage of the FreeBSD
+regression test suite, and released in the hope that others find it
+a useful tool to help improve the quality of their code.
+.Sh AUTHORS
+.An "Nik Clayton" Aq nik@ngo.org.uk ,
+.Aq nik@FreeBSD.org
+.Pp
+.Nm
+would not exist without the efforts of
+.An "Michael G Schwern" Aq schqern@pobox.com ,
+.An "Andy Lester" Aq andy@petdance.com ,
+and the countless others who have worked on the Perl QA programme.
+.Sh BUGS
+Ideally, running the tests would have no side effects on the behaviour
+of the application you are testing.  However, it is not always possible
+to avoid them.  The following side effects of using
+.Nm
+are known.
+.Bl -bullet -offset indent
+.It
+stdout is set to unbuffered mode after calling any of the
+.Fn plan_*
+functions.
+.El
Index: /tags/RHL_v1_1/psModules/test/tap/src/tap.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/src/tap.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/src/tap.c	(revision 9533)
@@ -0,0 +1,426 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <ctype.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tap.h"
+
+static int no_plan = 0;
+static int skip_all = 0;
+static int have_plan = 0;
+static unsigned int test_count = 0; /* Number of tests that have been run */
+static unsigned int e_tests = 0; /* Expected number of tests to run */
+static unsigned int failures = 0; /* Number of tests that failed */
+static char *todo_msg = NULL;
+static char *todo_msg_fixed = "libtap malloc issue";
+static int todo = 0;
+static int test_died = 0;
+
+/* Encapsulate the pthread code in a conditional.  In the absence of
+   libpthread the code does nothing */
+#ifdef HAVE_LIBPTHREAD
+#include <pthread.h>
+static pthread_mutex_t M = PTHREAD_MUTEX_INITIALIZER;
+# define LOCK pthread_mutex_lock(&M);
+# define UNLOCK pthread_mutex_unlock(&M);
+#else
+# define LOCK
+# define UNLOCK
+#endif
+
+static void _expected_tests(unsigned int);
+static void _tap_init(void);
+static void _cleanup(void);
+
+/*
+ * Generate a test result.
+ *
+ * ok -- boolean, indicates whether or not the test passed.
+ * test_name -- the name of the test, may be NULL
+ * test_comment -- a comment to print afterwards, may be NULL
+ */
+unsigned int
+_gen_result(int ok, const char *func, char *file, unsigned int line,
+            char *test_name, ...)
+{
+    va_list ap;
+    char *local_test_name = NULL;
+    char *c;
+    int name_is_digits;
+
+    LOCK;
+
+    test_count++;
+
+    /* Start by taking the test name and performing any printf()
+       expansions on it */
+    if(test_name != NULL) {
+        va_start(ap, test_name);
+        vasprintf(&local_test_name, test_name, ap);
+        va_end(ap);
+
+        /* Make sure the test name contains more than digits
+           and spaces.  Emit an error message and exit if it
+           does */
+        if(local_test_name) {
+            name_is_digits = 1;
+            for(c = local_test_name; *c != '\0'; c++) {
+                if(!isdigit(*c) && !isspace(*c)) {
+                    name_is_digits = 0;
+                    break;
+                }
+            }
+
+            if(name_is_digits) {
+                diag("    You named your test '%s'.  You shouldn't use numbers for your test names.", local_test_name);
+                diag("    Very confusing.");
+            }
+        }
+    }
+
+    if(!ok) {
+        printf("not ");
+        failures++;
+    }
+
+    printf("ok %d", test_count);
+
+    if(test_name != NULL) {
+        printf(" - ");
+
+        /* Print the test name, escaping any '#' characters it
+           might contain */
+        if(local_test_name != NULL) {
+            flockfile(stdout);
+            for(c = local_test_name; *c != '\0'; c++) {
+                if(*c == '#')
+                    fputc('\\', stdout);
+                fputc((int)*c, stdout);
+            }
+            funlockfile(stdout);
+        } else { /* vasprintf() failed, use a fixed message */
+            printf("%s", todo_msg_fixed);
+        }
+    }
+
+    /* If we're in a todo_start() block then flag the test as being
+       TODO.  todo_msg should contain the message to print at this
+       point.  If it's NULL then asprintf() failed, and we should
+       use the fixed message.
+
+       This is not counted as a failure, so decrement the counter if
+       the test failed. */
+    if(todo) {
+        printf(" # TODO %s", todo_msg ? todo_msg : todo_msg_fixed);
+        if(!ok)
+            failures--;
+    }
+
+    printf("\n");
+
+    if(!ok) {
+        if(getenv("HARNESS_ACTIVE") != NULL)
+            fputs("\n", stderr);
+
+        diag("    Failed %stest (%s:%s() at line %d)",
+             todo ? "(TODO) " : "", file, func, line);
+    }
+    free(local_test_name);
+
+    UNLOCK;
+
+    /* We only care (when testing) that ok is positive, but here we
+       specifically only want to return 1 or 0 */
+    return ok ? 1 : 0;
+}
+
+/*
+ * Initialise the TAP library.  Will only do so once, however many times it's
+ * called.
+ */
+void
+_tap_init(void)
+{
+    static int run_once = 0;
+
+    if(!run_once) {
+        atexit(_cleanup);
+
+        /* stdout needs to be unbuffered so that the output appears
+           in the same place relative to stderr output as it does 
+           with Test::Harness */
+        setbuf(stdout, 0);
+        run_once = 1;
+    }
+}
+
+/*
+ * Note that there's no plan.
+ */
+int
+plan_no_plan(void)
+{
+
+    LOCK;
+
+    _tap_init();
+
+    if(have_plan != 0) {
+        fprintf(stderr, "You tried to plan twice!\n");
+        test_died = 1;
+        UNLOCK;
+        exit(255);
+    }
+
+    have_plan = 1;
+    no_plan = 1;
+
+    UNLOCK;
+
+    return 1;
+}
+
+/*
+ * Note that the plan is to skip all tests
+ */
+int
+plan_skip_all(char *reason)
+{
+
+    LOCK;
+
+    _tap_init();
+
+    skip_all = 1;
+
+    printf("1..0");
+
+    if(reason != NULL)
+        printf(" # Skip %s", reason);
+
+    printf("\n");
+
+    UNLOCK;
+
+    exit(0);
+}
+
+/*
+ * Note the number of tests that will be run.
+ */
+int
+plan_tests(unsigned int tests)
+{
+
+    LOCK;
+
+    _tap_init();
+
+    if(have_plan != 0) {
+        fprintf(stderr, "You tried to plan twice!\n");
+        test_died = 1;
+        UNLOCK;
+        exit(255);
+    }
+
+    if(tests == 0) {
+        fprintf(stderr, "You said to run 0 tests!  You've got to run something.\n");
+        test_died = 1;
+        UNLOCK;
+        exit(255);
+    }
+
+    have_plan = 1;
+
+    _expected_tests(tests);
+
+    UNLOCK;
+
+    return e_tests;
+}
+
+unsigned int
+diag(char *fmt, ...)
+{
+    va_list ap;
+
+    fputs("# ", stderr);
+
+    va_start(ap, fmt);
+    vfprintf(stderr, fmt, ap);
+    va_end(ap);
+
+    fputs("\n", stderr);
+
+    return 0;
+}
+
+void
+_expected_tests(unsigned int tests)
+{
+
+    printf("1..%d\n", tests);
+    e_tests = tests;
+}
+
+int
+skip(unsigned int n, char *fmt, ...)
+{
+    va_list ap;
+    char *skip_msg;
+
+    LOCK;
+
+    va_start(ap, fmt);
+    asprintf(&skip_msg, fmt, ap);
+    va_end(ap);
+
+    while(n-- > 0) {
+        test_count++;
+        printf("ok %d # skip %s\n", test_count,
+               skip_msg != NULL ?
+               skip_msg : "libtap():malloc() failed");
+    }
+
+    free(skip_msg);
+
+    UNLOCK;
+
+    return 1;
+}
+
+void
+todo_start(char *fmt, ...)
+{
+    va_list ap;
+
+    LOCK;
+
+    va_start(ap, fmt);
+    vasprintf(&todo_msg, fmt, ap);
+    va_end(ap);
+
+    todo = 1;
+
+    UNLOCK;
+}
+
+void
+todo_end(void)
+{
+
+    LOCK;
+
+    todo = 0;
+    free(todo_msg);
+
+    UNLOCK;
+}
+
+int
+exit_status(void)
+{
+    int r;
+
+    LOCK;
+
+    /* If there's no plan, just return the number of failures */
+    if(no_plan || !have_plan) {
+        UNLOCK;
+        return failures;
+    }
+
+    /* Ran too many tests?  Return the number of tests that were run
+       that shouldn't have been */
+    if(e_tests < test_count) {
+        r = test_count - e_tests;
+        UNLOCK;
+        return r;
+    }
+
+    /* Return the number of tests that failed + the number of tests
+       that weren't run */
+    r = failures + e_tests - test_count;
+    UNLOCK;
+
+    return r;
+}
+
+/*
+ * Cleanup at the end of the run, produce any final output that might be
+ * required.
+ */
+void
+_cleanup(void)
+{
+
+    LOCK;
+
+    /* If plan_no_plan() wasn't called, and we don't have a plan,
+       and we're not skipping everything, then something happened
+       before we could produce any output */
+    if(!no_plan && !have_plan && !skip_all) {
+        diag("Looks like your test died before it could output anything.");
+        UNLOCK;
+        return;
+    }
+
+    if(test_died) {
+        diag("Looks like your test died just after %d.", test_count);
+        UNLOCK;
+        return;
+    }
+
+
+    /* No plan provided, but now we know how many tests were run, and can
+       print the header at the end */
+    if(!skip_all && (no_plan || !have_plan)) {
+        printf("1..%d\n", test_count);
+    }
+
+    if((have_plan && !no_plan) && e_tests < test_count) {
+        diag("Looks like you planned %d %s but ran %d extra.",
+             e_tests, e_tests == 1 ? "test" : "tests", test_count - e_tests);
+        UNLOCK;
+        return;
+    }
+
+    if((have_plan || !no_plan) && e_tests > test_count) {
+        diag("Looks like you planned %d %s but only ran %d.",
+             e_tests, e_tests == 1 ? "test" : "tests", test_count);
+        UNLOCK;
+        return;
+    }
+
+    if(failures)
+        diag("Looks like you failed %d %s of %d.",
+             failures, failures == 1 ? "test" : "tests", test_count);
+
+    UNLOCK;
+}
Index: /tags/RHL_v1_1/psModules/test/tap/src/tap.h
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/src/tap.h	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/src/tap.h	(revision 9533)
@@ -0,0 +1,89 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/* '## __VA_ARGS__' is a gcc'ism. C99 doesn't allow the token pasting
+   and requires the caller to add the final comma if they've ommitted
+   the optional arguments */
+#ifdef __GNUC__
+# define ok(e, test, ...) ((e) ?     \
+                           _gen_result(1, __func__, __FILE__, __LINE__, \
+                                       test, ## __VA_ARGS__) :  \
+                           _gen_result(0, __func__, __FILE__, __LINE__, \
+                                       test, ## __VA_ARGS__))
+
+# define ok1(e) ((e) ?       \
+                 _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \
+                 _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e))
+
+# define pass(test, ...) ok(1, test, ## __VA_ARGS__);
+# define fail(test, ...) ok(0, test, ## __VA_ARGS__);
+
+# define skip_start(test, n, fmt, ...)   \
+do {      \
+    if((test)) {    \
+        skip(n, fmt, ## __VA_ARGS__); \
+        continue;   \
+    }
+    #elif __STDC_VERSION__ >= 199901L /* __GNUC__ */
+    # define ok(e, ...) ((e) ?      \
+                         _gen_result(1, __func__, __FILE__, __LINE__, \
+                                     __VA_ARGS__) :    \
+                         _gen_result(0, __func__, __FILE__, __LINE__, \
+                                     __VA_ARGS__))
+
+    # define ok1(e) ((e) ?       \
+                     _gen_result(1, __func__, __FILE__, __LINE__, "%s", #e) : \
+                     _gen_result(0, __func__, __FILE__, __LINE__, "%s", #e))
+
+    # define pass(...) ok(1, __VA_ARGS__);
+    # define fail(...) ok(0, __VA_ARGS__);
+
+    # define skip_start(test, n, ...)   \
+    do {      \
+        if((test)) {    \
+            skip(n,  __VA_ARGS__);  \
+            continue;   \
+        }
+        #else /* __STDC_VERSION__ */
+        # error "Needs gcc or C99 compiler for variadic macros."
+        #endif /* __STDC_VERSION__ */
+
+        #define skip_end() } while(0);
+
+    unsigned int _gen_result(int, const char *, char *, unsigned int, char *, ...);
+
+    int plan_no_plan(void);
+    int plan_skip_all(char *);
+    int plan_tests(unsigned int);
+
+    unsigned int diag(char *, ...);
+
+    int skip(unsigned int, char *, ...);
+
+    void todo_start(char *, ...);
+    void todo_end(void);
+
+    int exit_status(void);
Index: /tags/RHL_v1_1/psModules/test/tap/tests/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/.cvsignore	(revision 9533)
@@ -0,0 +1,6 @@
+Makefile
+Makefile.in
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/Makefile.am	(revision 9533)
@@ -0,0 +1,7 @@
+SUBDIRS=	diag
+SUBDIRS+=	fail
+SUBDIRS+=	ok
+SUBDIRS+=	pass
+SUBDIRS+=	plan
+SUBDIRS+=	skip
+SUBDIRS+=	todo
Index: /tags/RHL_v1_1/psModules/test/tap/tests/README
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/README	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/README	(revision 9533)
@@ -0,0 +1,12 @@
+Most of the tests follow the same pattern.
+
+ * test.pl that uses Test::More, and demonstrates whatever functionality 
+   that we're trying to test.  This is the reference code.
+
+ * test.c, which tests the libtap reimplementation of the same functionality.
+
+ * test.t, which compiles the .c program, runs both test scripts, and then 
+   diffs their output to make sure it's identical.
+
+   Right now, test.t is identical in every directory.  This sucks somewhat.
+   It should either be a symlink to a common script
Index: /tags/RHL_v1_1/psModules/test/tap/tests/diag/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/diag/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/diag/.cvsignore	(revision 9533)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/diag/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/diag/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/diag/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/diag/test.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/diag/test.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/diag/test.c	(revision 9533)
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    plan_tests(2);
+
+    rc = diag("A diagnostic message");
+    diag("Returned: %d", rc);
+
+    /* Make sure the failure is passed through */
+    ok(1, "test 1") || diag("ok() failed, and shouldn't");
+    ok(0, "test 2") || diag("ok() passed, and shouldn't");
+
+    return exit_status();
+}
Index: /tags/RHL_v1_1/psModules/test/tap/tests/diag/test.pl
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/diag/test.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/diag/test.pl	(revision 9533)
@@ -0,0 +1,16 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+plan tests => 2;
+
+$rc = diag("A diagnostic message");
+diag("Returned: $rc");
+
+ok(1, 'test 1') or diag "ok() failed, and shouldn't";
+ok(0, 'test 2') or diag "ok() passed, and shouldn't";
Index: /tags/RHL_v1_1/psModules/test/tap/tests/diag/test.t
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/diag/test.t	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/diag/test.t	(revision 9533)
@@ -0,0 +1,67 @@
+#!/bin/sh
+
+echo '1..7'
+
+perl $srcdir/test.pl 2>/dev/null >test.pl.out
+perlstatus=$?
+
+./test > test.c.out 2>&1
+cstatus=$?
+
+if grep "^# A diagnostic message$" test.c.out > /dev/null ; then
+    echo "ok 1 - found a diagnostic message"
+else
+    echo "not ok 1 - found a diagnostic message"
+    retval=1
+fi
+
+if grep "^# Returned: 0$" test.c.out > /dev/null ; then
+    echo "ok 2 - diag() expected return value" 
+else
+    echo "not ok 2 - diag() expected return value" 
+    retval=1
+fi
+
+if grep "^#     Failed test (.*test.c:main() at line 43)$" test.c.out > /dev/null ; then
+    echo "ok 3 - 'failed test at line' diag" 
+else
+    echo "not ok 3 - 'failed test at line' diag" 
+    retval=1
+fi
+
+if grep "^# ok() passed, and shouldn't$" test.c.out > /dev/null ; then
+    echo "ok 4 - expected diag"
+else
+    echo "ok 4 - expected diag"
+    retval=1
+fi
+
+if grep "^# Looks like you failed 1 test of 2.$" test.c.out > /dev/null ; then
+    echo "ok 5 - failed 1 test"
+ else
+    echo "ok 5 - failed 1 test"
+    retval=1
+fi
+
+sed -e '/^#/D' test.c.out > tmp
+mv tmp test.c.out
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 6 - TAP output is identical'
+else
+	retval=1
+	echo 'not ok 6 - TAP output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 7 - status code'
+else
+	retval=1
+	echo 'not ok 7 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /tags/RHL_v1_1/psModules/test/tap/tests/fail/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/fail/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/fail/.cvsignore	(revision 9533)
@@ -0,0 +1,15 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/fail/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/fail/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/fail/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/fail/test.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/fail/test.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/fail/test.c	(revision 9533)
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(2);
+    diag("Returned: %d", rc);
+
+    rc = fail("test to fail");
+    diag("Returned: %d", rc);
+
+    rc = fail("test to fail %s", "with extra string");
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /tags/RHL_v1_1/psModules/test/tap/tests/fail/test.pl
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/fail/test.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/fail/test.pl	(revision 9533)
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 2;
+diag("Returned: " . sprintf('%d', $rc));
+
+$rc = fail('test to fail');
+diag("Returned: $rc");
+
+$rc = fail('test to fail with extra string');
+diag("Returned: $rc");
Index: /tags/RHL_v1_1/psModules/test/tap/tests/fail/test.t
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/fail/test.t	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/fail/test.t	(revision 9533)
@@ -0,0 +1,74 @@
+#!/bin/sh
+
+echo '1..8'
+
+perl $srcdir/test.pl 2>/dev/null > test.pl.out
+perlstatus=$?
+
+./test > test.c.out 2>&1 
+cstatus=$?
+
+if grep "^# Returned: 2$" test.c.out >/dev/null ; then
+  echo "ok 1 - expected return value";
+else
+  echo "not ok 1 - expected return value";
+  retval=1
+fi
+
+if grep "^#     Failed test (.*test.c:main() at line 39)$" test.c.out >/dev/null ; then
+  echo "ok 2 - failed expected test";
+else
+  echo "not ok 2 - failed expected test";
+  retval=1
+fi
+
+if grep "^# Returned: 0$" test.c.out >/dev/null ; then
+  echo "ok 3 - expected return value";
+else
+  echo "not ok 3 - expected return value";
+  retval=1
+fi
+
+if grep "^#     Failed test (.*test.c:main() at line 42)$" test.c.out >/dev/null ; then
+  echo "ok 4 - failed expected test";
+else
+  echo "not ok 4 - failed expected test";
+  retval=1
+fi
+  
+if grep "^# Returned: 0$" test.c.out >/dev/null ; then
+  echo "ok 5 - expected return value";
+else
+  echo "not ok 5 - expected return value";
+  retval=1
+fi
+
+if grep "^# Looks like you failed 2 tests of 2.$" test.c.out >/dev/null ; then
+  echo "ok 6 - expected return value";
+else
+  echo "not ok 6 - expected return value";
+  retval=1
+fi
+
+sed -e '/^#/D' test.c.out > tmp
+mv tmp test.c.out
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 7 - output is identical'
+else
+	retval=1
+	echo 'not ok 7 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 8 - status code'
+else
+	retval=1
+	echo 'not ok 8 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/.cvsignore	(revision 9533)
@@ -0,0 +1,6 @@
+Makefile
+Makefile.in
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/Makefile.am	(revision 9533)
@@ -0,0 +1,3 @@
+SUBDIRS  =	ok
+SUBDIRS +=	ok-hash
+SUBDIRS +=	ok-numeric
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-hash/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-hash/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-hash/.cvsignore	(revision 9533)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-hash/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-hash/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-hash/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-hash/test.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-hash/test.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-hash/test.c	(revision 9533)
@@ -0,0 +1,52 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(4);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, "Test with no hash");
+    diag("Returned: %d", rc);
+
+    rc = ok(1, "Test with one # hash");
+    diag("Returned: %d", rc);
+
+    rc = ok(1, "Test with # two # hashes");
+    diag("Returned: %d", rc);
+
+    rc = ok(1, "Test with ## back to back hashes");
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-hash/test.pl
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-hash/test.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-hash/test.pl	(revision 9533)
@@ -0,0 +1,24 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 4;
+diag("Returned: " . sprintf("%d", $rc));
+
+
+$rc = ok(1, 'Test with no hash');
+diag("Returned: $rc");
+
+$rc = ok(1, 'Test with one # hash');
+diag("Returned: $rc");
+
+$rc = ok(1, 'Test with # two # hashes');
+diag("Returned: $rc");
+
+$rc = ok(1, 'Test with ## back to back hashes');
+diag("Returned: $rc");
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-hash/test.t
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-hash/test.t	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-hash/test.t	(revision 9533)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-numeric/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-numeric/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-numeric/.cvsignore	(revision 9533)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-numeric/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-numeric/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-numeric/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-numeric/test.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-numeric/test.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-numeric/test.c	(revision 9533)
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(3);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, "First test");
+    diag("Returned: %d", rc);
+
+    rc = ok(1, "1");
+    diag("Returned: %d", rc);
+
+    rc = ok(1, "Third test");
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-numeric/test.pl
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-numeric/test.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-numeric/test.pl	(revision 9533)
@@ -0,0 +1,21 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 3;
+diag("Returned: " . sprintf("%d", $rc));
+
+
+$rc = ok(1, 'First test');
+diag("Returned: $rc");
+
+$rc = ok(1, '1');
+diag("Returned: $rc");
+
+$rc = ok(1, 'Third test');
+diag("Returned: $rc");
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-numeric/test.t
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-numeric/test.t	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok-numeric/test.t	(revision 9533)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok/.cvsignore	(revision 9533)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok/test.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok/test.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok/test.c	(revision 9533)
@@ -0,0 +1,55 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(5);
+    diag("Returned: %d", rc);
+
+    rc = ok(1 == 1, "1 equals 1");
+    diag("Returned: %d", rc);
+
+    rc = ok(1 == 1, "1 equals %d", 1);
+    diag("Returned: %d", rc);
+
+    rc = ok1(1 == 1);
+    diag("Returned: %d", rc);
+
+    rc = ok(1 == 2, "1 equals 2");
+    diag("Returned: %d", rc);
+
+    rc = ok1(1 == 2);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok/test.pl
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok/test.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok/test.pl	(revision 9533)
@@ -0,0 +1,27 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 5;
+diag("Returned: " . sprintf("%d", $rc));
+
+
+$rc = ok(1 == 1, '1 equals 1');	# Test ok() passes when it should
+diag("Returned: $rc");
+
+$rc = ok(1 == 1, '1 equals 1'); # Used for %d testing in test.c
+diag("Returned: $rc");
+
+$rc = ok(1 == 1, '1 == 1');	# Test ok1() passes when it should
+diag("Returned: $rc");
+
+$rc = ok(1 == 2, '1 equals 2');	# Test ok() fails when it should
+diag("Returned: $rc");
+
+$rc = ok(1 == 2, '1 == 2');	# Test ok1() fails when it should
+diag("Returned: $rc");
Index: /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok/test.t
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok/test.t	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/ok/ok/test.t	(revision 9533)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /tags/RHL_v1_1/psModules/test/tap/tests/pass/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/pass/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/pass/.cvsignore	(revision 9533)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/pass/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/pass/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/pass/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/pass/test.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/pass/test.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/pass/test.c	(revision 9533)
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(2);
+    diag("Returned: %d", rc);
+
+    rc = pass("test to pass");
+    diag("Returned: %d", rc);
+
+    rc = pass("test to pass %s", "with extra string");
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /tags/RHL_v1_1/psModules/test/tap/tests/pass/test.pl
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/pass/test.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/pass/test.pl	(revision 9533)
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 2;
+diag("Returned: " . sprintf('%d', $rc));
+
+$rc = pass('test to pass');
+diag("Returned: $rc");
+
+$rc = pass('test to pass with extra string');
+diag("Returned: $rc");
Index: /tags/RHL_v1_1/psModules/test/tap/tests/pass/test.t
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/pass/test.t	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/pass/test.t	(revision 9533)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/.cvsignore	(revision 9533)
@@ -0,0 +1,6 @@
+Makefile
+Makefile.in
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/Makefile.am	(revision 9533)
@@ -0,0 +1,7 @@
+SUBDIRS  =	no-tests
+SUBDIRS +=	no_plan
+SUBDIRS +=	not-enough-tests
+SUBDIRS +=	too-many-plans
+SUBDIRS +=	too-many-tests
+SUBDIRS +=	sane
+SUBDIRS +=	skip_all
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/no-tests/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/no-tests/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/no-tests/.cvsignore	(revision 9533)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/no-tests/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/no-tests/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/no-tests/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/no-tests/test.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/no-tests/test.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/no-tests/test.c	(revision 9533)
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(0);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, NULL);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/no-tests/test.pl
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/no-tests/test.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/no-tests/test.pl	(revision 9533)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 0;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/no-tests/test.t
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/no-tests/test.t	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/no-tests/test.t	(revision 9533)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/no_plan/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/no_plan/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/no_plan/.cvsignore	(revision 9533)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/no_plan/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/no_plan/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/no_plan/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/no_plan/test.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/no_plan/test.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/no_plan/test.c	(revision 9533)
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_no_plan();
+    diag("Returned: %d", rc);
+
+    rc = ok(1, NULL);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/no_plan/test.pl
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/no_plan/test.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/no_plan/test.pl	(revision 9533)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+my $rc = 0;
+
+use Test::More;
+
+$rc = plan qw(no_plan);
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/no_plan/test.t
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/no_plan/test.t	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/no_plan/test.t	(revision 9533)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/not-enough-tests/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/not-enough-tests/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/not-enough-tests/.cvsignore	(revision 9533)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/not-enough-tests/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/not-enough-tests/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/not-enough-tests/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/not-enough-tests/test.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/not-enough-tests/test.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/not-enough-tests/test.c	(revision 9533)
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(1);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, NULL);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, NULL);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, NULL);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/not-enough-tests/test.pl
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/not-enough-tests/test.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/not-enough-tests/test.pl	(revision 9533)
@@ -0,0 +1,20 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 1;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
+
+$rc = ok(1);
+diag("Returned: $rc");
+
+$rc = ok(1);
+diag("Returned: $rc");
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/not-enough-tests/test.t
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/not-enough-tests/test.t	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/not-enough-tests/test.t	(revision 9533)
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	# comment this out until we're exit-code compatible with Test::More
+	#retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/sane/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/sane/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/sane/.cvsignore	(revision 9533)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/sane/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/sane/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/sane/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/sane/test.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/sane/test.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/sane/test.c	(revision 9533)
@@ -0,0 +1,43 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(1);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, NULL);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/sane/test.pl
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/sane/test.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/sane/test.pl	(revision 9533)
@@ -0,0 +1,14 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 1;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/sane/test.t
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/sane/test.t	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/sane/test.t	(revision 9533)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/.cvsignore	(revision 9533)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/plan.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/plan.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/plan.c	(revision 9533)
@@ -0,0 +1,39 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "tap.h"
+
+/* Run pre-defined tests on the test library to make sure that the basic
+   functionality works, and it can be used to test itself afterwards */
+
+int
+main(int argc, char *argv[])
+{
+    plan_skip_all("No good reason");
+}
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/test.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/test.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/test.c	(revision 9533)
@@ -0,0 +1,38 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_skip_all("No good reason");
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/test.pl
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/test.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/test.pl	(revision 9533)
@@ -0,0 +1,11 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan skip_all => "No good reason";
+diag("Returned: " . sprintf("%d", $rc));
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/test.t
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/test.t	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/skip_all/test.t	(revision 9533)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-plans/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-plans/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-plans/.cvsignore	(revision 9533)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-plans/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-plans/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-plans/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-plans/test.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-plans/test.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-plans/test.c	(revision 9533)
@@ -0,0 +1,49 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(1);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, NULL);
+    diag("Returned: %d", rc);
+
+    rc = plan_tests(1);
+    diag("Returned: %d", rc);
+
+    rc = ok(0, NULL);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-plans/test.pl
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-plans/test.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-plans/test.pl	(revision 9533)
@@ -0,0 +1,20 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 1;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
+
+$rc = plan tests => 1;
+diag("Returned: $rc");
+
+$rc = ok(0);
+diag("Returned: $rc");
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-plans/test.t
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-plans/test.t	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-plans/test.t	(revision 9533)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-tests/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-tests/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-tests/.cvsignore	(revision 9533)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-tests/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-tests/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-tests/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-tests/test.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-tests/test.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-tests/test.c	(revision 9533)
@@ -0,0 +1,46 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+
+    rc = plan_tests(5);
+    diag("Returned: %d", rc);
+
+    rc = ok(1, NULL);
+    diag("Returned: %d", rc);
+
+    rc = ok(0, NULL);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-tests/test.pl
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-tests/test.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-tests/test.pl	(revision 9533)
@@ -0,0 +1,17 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 5;
+diag("Returned: " . sprintf("%d", $rc));
+
+$rc = ok(1);
+diag("Returned: $rc");
+
+$rc = ok(0);
+diag("Returned: $rc");
Index: /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-tests/test.t
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-tests/test.t	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/plan/too-many-tests/test.t	(revision 9533)
@@ -0,0 +1,30 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+    # we're not exit-status compatible with Test::More yet
+	#retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /tags/RHL_v1_1/psModules/test/tap/tests/skip/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/skip/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/skip/.cvsignore	(revision 9533)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/skip/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/skip/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/skip/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/skip/test.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/skip/test.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/skip/test.c	(revision 9533)
@@ -0,0 +1,69 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+    unsigned int side_effect = 0;
+
+    rc = plan_tests(4);
+    diag("Returned: %d", rc);
+
+    rc = ok(1 == 1, "1 equals 1"); /* Should always work */
+    diag("Returned: %d", rc);
+
+    do {
+        if(1) {
+            rc = skip(1, "Testing skipping");
+            continue;
+        }
+
+        side_effect++;
+
+        ok(side_effect == 1, "side_effect checked out");
+
+    } while(0);
+
+    diag("Returned: %d", rc);
+
+    skip_start(1 == 1, 1, "Testing skipping #2");
+
+    side_effect++;
+    rc = ok(side_effect == 1, "side_effect checked out");
+    diag("Returned: %d", rc);
+
+    skip_end();
+
+    rc = ok(side_effect == 0, "side_effect is %d", side_effect);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /tags/RHL_v1_1/psModules/test/tap/tests/skip/test.pl
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/skip/test.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/skip/test.pl	(revision 9533)
@@ -0,0 +1,40 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 4;
+diag("Returned: " . sprintf("%d", $rc));
+
+my $side_effect = 0;		# Check whether skipping has side effects
+
+$rc = ok(1 == 1, '1 equals 1');	# Test ok() passes when it should
+diag("Returned: $rc");
+
+# Start skipping
+SKIP: {
+	$rc = skip "Testing skipping", 1;
+
+	$side_effect++;
+
+	$rc = ok($side_effect == 1, '$side_effect checked out');
+}
+
+diag("Returned: $rc");
+
+SKIP: {
+	$rc = skip "Testing skipping #2", 1;
+	diag("Returned: $rc");
+
+	$side_effect++;
+
+	$rc = ok($side_effect == 1, '$side_effect checked out');
+	diag("Returned: $rc");
+}
+
+$rc = ok($side_effect == 0, "side_effect is $side_effect");
+diag("Returned: $rc");
Index: /tags/RHL_v1_1/psModules/test/tap/tests/skip/test.t
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/skip/test.t	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/skip/test.t	(revision 9533)
@@ -0,0 +1,29 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /tags/RHL_v1_1/psModules/test/tap/tests/todo/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/todo/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/todo/.cvsignore	(revision 9533)
@@ -0,0 +1,11 @@
+.deps
+Makefile
+Makefile.in
+.libs
+test
+test.c.out
+test.pl.out
+*.bb
+*.bbg
+*.da
+gmon.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/todo/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/todo/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/todo/Makefile.am	(revision 9533)
@@ -0,0 +1,13 @@
+
+TESTS = 		test.t
+TESTS_ENVIRONMENT =	$(SHELL)
+
+EXTRA_DIST = 		$(TESTS) test.pl
+
+check_PROGRAMS = 	test
+
+test_CFLAGS = 		-g -I$(top_srcdir)/src
+test_LDFLAGS = 		-L$(top_builddir)/src
+test_LDADD = 		-ltap
+
+CLEANFILES =	test.o test.c.out test.pl.out
Index: /tags/RHL_v1_1/psModules/test/tap/tests/todo/test.c
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/todo/test.c	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/todo/test.c	(revision 9533)
@@ -0,0 +1,68 @@
+/*-
+ * Copyright (c) 2004 Nik Clayton
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdio.h>
+
+#include "tap.h"
+
+int
+main(int argc, char *argv[])
+{
+    unsigned int rc = 0;
+    unsigned int side_effect = 0;
+
+    rc = plan_tests(5);
+    diag("Returned: %d", rc);
+
+    rc = ok(1 == 1, "1 equals 1"); /* Should always work */
+    diag("Returned: %d", rc);
+
+    todo_start("For testing purposes");
+
+    side_effect++;
+
+    /* This test should fail */
+    rc = ok(side_effect == 0, "side_effect checked out");
+    diag("Returned: %d", rc);
+
+    /* This test should unexpectedly succeed */
+    rc = ok(side_effect == 1, "side_effect checked out");
+    diag("Returned: %d", rc);
+
+    todo_end();
+
+    todo_start("Testing printf() %s in todo_start()", "expansion");
+
+    rc = ok(0, "dummy test");
+    diag("Returned: %d", rc);
+
+    todo_end();
+
+    rc = ok(side_effect == 1, "side_effect is %d", side_effect);
+    diag("Returned: %d", rc);
+
+    return exit_status();
+}
Index: /tags/RHL_v1_1/psModules/test/tap/tests/todo/test.pl
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/todo/test.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/todo/test.pl	(revision 9533)
@@ -0,0 +1,41 @@
+#!/usr/bin/perl
+
+use warnings;
+use strict;
+
+use Test::More;
+
+my $rc = 0;
+
+$rc = plan tests => 5;
+diag("Returned: " . sprintf("%d", $rc));
+
+my $side_effect = 0;		# Check whether TODO has side effects
+
+$rc = ok(1 == 1, '1 equals 1');	# Test ok() passes when it should
+diag("Returned: $rc");
+
+# Start TODO tests
+TODO: {
+	local $TODO = 'For testing purposes';
+
+	$side_effect++;
+
+	# This test should fail
+	$rc = ok($side_effect == 0, 'side_effect checked out');
+	diag("Returned: $rc");
+
+	# This test should unexpectedly succeed
+	$rc = ok($side_effect == 1, 'side_effect checked out');
+	diag("Returned: $rc");
+}
+
+TODO: {
+	local $TODO = 'Testing printf() expansion in todo_start()';
+
+	$rc = ok(0, 'dummy test');
+	diag("Returned: $rc");
+}
+
+$rc = ok($side_effect == 1, "side_effect is $side_effect");
+diag("Returned: $rc");
Index: /tags/RHL_v1_1/psModules/test/tap/tests/todo/test.t
===================================================================
--- /tags/RHL_v1_1/psModules/test/tap/tests/todo/test.t	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/tap/tests/todo/test.t	(revision 9533)
@@ -0,0 +1,34 @@
+#!/bin/sh
+
+echo '1..2'
+
+perl $srcdir/test.pl 2> /dev/null > test.pl.out
+perlstatus=$?
+
+# Test:;More prints diagnostic from TODO tests on stdout
+# http://rt.cpan.org/Ticket/Display.html?id=14982
+sed '/^#/D' test.pl.out > tmp
+mv tmp test.pl.out
+
+./test 2> /dev/null > test.c.out
+cstatus=$?
+
+diff -u test.pl.out test.c.out
+
+if [ $? -eq 0 ]; then
+	echo 'ok 1 - output is identical'
+else
+	retval=1
+	echo 'not ok 1 - output is identical'
+fi
+
+if [ $perlstatus -eq $cstatus ]; then
+	echo 'ok 2 - status code'
+else
+	retval=1
+	echo 'not ok 2 - status code'
+	echo "# perlstatus = $perlstatus"
+	echo "#    cstatus = $cstatus"
+fi
+
+exit $retval
Index: /tags/RHL_v1_1/psModules/test/test.pl
===================================================================
--- /tags/RHL_v1_1/psModules/test/test.pl	(revision 9533)
+++ /tags/RHL_v1_1/psModules/test/test.pl	(revision 9533)
@@ -0,0 +1,31 @@
+#!/usr/bin/env perl
+
+# Copyright (C) 2006  Joshua Hoblitt
+#
+# $Id: test.pl,v 1.1 2006-09-23 02:47:58 magnier Exp $
+
+use strict;
+use warnings FATAL => qw( all);
+
+use vars qw($VERSION);
+$VERSION = '0.01';
+
+use File::Find::Rule;
+use Cwd;
+
+my $rule = File::Find::Rule->new;
+# ignore .lib directories
+$rule->or($rule->new
+        ->directory
+        ->name('.libs')
+        ->prune
+        ->discard,
+        $rule->new
+    );
+$rule->name(qr/^tap_[^.]*$/)
+        ->maxdepth(2)
+        ->relative;
+
+my @test_files = $rule->in(getcwd());
+
+system("prove @test_files");
Index: /tags/RHL_v1_1/psphot/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psphot/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psphot/.cvsignore	(revision 9533)
@@ -0,0 +1,18 @@
+bin
+Makefile
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.log
+config.status
+configure
+depcomp
+install-sh
+missing
+config.guess
+libtool
+ltmain.sh
+stamp-h1
+config.sub
+psphot.pc
Index: /tags/RHL_v1_1/psphot/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psphot/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psphot/Makefile.am	(revision 9533)
@@ -0,0 +1,10 @@
+SUBDIRS = src
+
+CLEANFILES = *.pyc *~ core core.*
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= psphot.pc
+
+EXTRA_DIST = \
+	psphot.pc.in \
+	autogen.sh
Index: /tags/RHL_v1_1/psphot/autogen.sh
===================================================================
--- /tags/RHL_v1_1/psphot/autogen.sh	(revision 9533)
+++ /tags/RHL_v1_1/psphot/autogen.sh	(revision 9533)
@@ -0,0 +1,103 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=psphot
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL=aclocal
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOlIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOlIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE  failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /tags/RHL_v1_1/psphot/configure.ac
===================================================================
--- /tags/RHL_v1_1/psphot/configure.ac	(revision 9533)
+++ /tags/RHL_v1_1/psphot/configure.ac	(revision 9533)
@@ -0,0 +1,58 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.59)
+
+AC_INIT([psphot], [0.0.1], [http://pan-starrs.ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([src])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+
+dnl enable largefile supports
+AC_SYS_LARGEFILE
+
+dnl handle debug building
+AC_ARG_ENABLE(optimize,
+  [AS_HELP_STRING(--enable-optimize,enable compiler optimization)],
+  [AC_MSG_RESULT(compile optimization enabled)
+   CFLAGS="-O2"],
+  [AC_MSG_RESULT([compile optimization disabled])
+   CFLAGS="-O0 -g"]
+)
+
+dnl handle profiler building
+AC_ARG_ENABLE(profile,
+  [AS_HELP_STRING(--enable-profile,enable compiler profiler information inclusion)],
+  [AC_MSG_RESULT(compile optimization enabled)
+   CFLAGS="${CFLAGS=} -pg"]
+)
+
+dnl ------------------------------------------------------------
+
+AC_PATH_PROG([ERRORCODES], [psParseErrorCodes], [missing])
+if test "$ERRORCODES" = "missing" ; then
+  AC_MSG_ERROR([psParseErrorCodes is required])
+fi
+
+PKG_CHECK_MODULES(PSLIB, pslib >= 0.12.99)
+PKG_CHECK_MODULES(PSMODULE, psmodules >= 0.12.99)
+
+dnl Set CFLAGS for build
+CFLAGS="${CFLAGS} -Wall -Werror -std=c99"
+
+AC_SUBST([PSPHOT_CFLAGS])
+AC_SUBST([PSPHOT_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  psphot.pc
+])
+
+AC_OUTPUT
Index: /tags/RHL_v1_1/psphot/doc/config.txt
===================================================================
--- /tags/RHL_v1_1/psphot/doc/config.txt	(revision 9533)
+++ /tags/RHL_v1_1/psphot/doc/config.txt	(revision 9533)
@@ -0,0 +1,56 @@
+
+# input data options
+# IMAGE             STR    file.fits
+# MASK_IMAGE	    STR    mask.fits
+# WEIGHT_IMAGE	    STR	   weight.fits
+
+# output data options
+OUTPUT_FILE	    STR	   output.smp    # output object file
+OUTPUT_MODE	    STR	   TEXT          # output mode: TEXT, OBJ, SX, CMP, CMF 
+# RESID_IMAGE	    STR	   resid.fits    # output residual image
+
+# image noise parameters
+RDNOISE             STR  HD:RDNOISE      # read-noise in electrons
+GAIN                STR  HD:GAIN         # electrons / DN
+
+# masking parameters
+XMIN                F32  32        	 # minimum valid x-coord
+XMAX                F32   0        	 # maximum valid x-coord
+YMIN                F32   1        	 # minimum valid y-coord
+YMAX                F32   0        	 # maximum valid y-coord
+SATURATION          F32  50000     	 # saturation level on this chip
+
+# image statistics parameters
+IMSTATS_NPIX        S32  100000    	 # number of pixels to use for sky estimate:
+
+# peak finding 
+PEAKS_SMOOTH_SIGMA  F32  1.0       	 # smoothing kernel sigma in pixels
+PEAKS_SMOOTH_NSIGMA F32  3.0   	   	 # smoothing kernel width in sigmas
+PEAKS_NSIGMA_LIMIT  F32  10.0  	   	 # peak significance threshold
+
+# basic object statistics
+SKY_INNER_RADIUS    F32  15		 # square annulus for local sky measurement
+SKY_OUTER_RADIUS    F32  25		 # square annulus for local sky measurement
+PSF_MOMENTS_RADIUS  F32  5
+PSF_SN_LIM          F32  100
+
+# PSF model parameters : choose the PSF model
+# list as many PSF_MODEL options as desired
+PSF_MODEL           STR  PS_MODEL_QGAUSS
+#PSF_MODEL           STR  PS_MODEL_PGAUSS
+#PSF_MODEL           STR  PS_MODEL_GAUSS
+PSF_FIT_RADIUS      F32  25		 # fitting radius for test PSF model
+
+# PSF model parameters : apply the PSF model
+PSF_FIT_NSIGMA       F32  1		 # significance for pixel included in fit
+PSF_FIT_PADDING      F32  5              # extra annulus to use for fit 
+PSF_SHAPE_NSIGMA     F32  3.0		 # max significance for shape variation
+PSF_MIN_SN           F32  2.0		 # reject objects below this significance
+PSF_MAX_CHI          F32  10.0		 # reject objects worse that this
+
+# Galaxy model parameters
+GAL_MODEL            STR  PS_MODEL_SGAUSS
+GAL_MIN_SN           F32  3
+GAL_FIT_NSIGMA       F32  1		 # significance for pixel included in fit
+GAL_FIT_PADDING      F32  5              # extra annulus to use for fit 
+GAL_MOMENTS_RADIUS   F32  9
Index: /tags/RHL_v1_1/psphot/doc/notes.txt
===================================================================
--- /tags/RHL_v1_1/psphot/doc/notes.txt	(revision 9533)
+++ /tags/RHL_v1_1/psphot/doc/notes.txt	(revision 9533)
@@ -0,0 +1,386 @@
+
+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: /tags/RHL_v1_1/psphot/doc/output.txt
===================================================================
--- /tags/RHL_v1_1/psphot/doc/output.txt	(revision 9533)
+++ /tags/RHL_v1_1/psphot/doc/output.txt	(revision 9533)
@@ -0,0 +1,15 @@
+
+we have several output formats:
+  - TEXT : separate ASCII tables for PSF, EXT, MNT, and NULL
+  - SX : sextractor-style ASCII file
+  - OBJ : dophot-style ASCII file
+  - CMP : old-style elixir format (header + text)
+  - CMF : fits-table
+
+we have several options for organization:
+  - single chip/readout : outroot.ext
+  - split chips : outroot.NN.ext
+  - mef chips : outroot.ext (extensions with names)
+
+  * only CMF will work with MEF model
+
Index: /tags/RHL_v1_1/psphot/doc/psphot.txt
===================================================================
--- /tags/RHL_v1_1/psphot/doc/psphot.txt	(revision 9533)
+++ /tags/RHL_v1_1/psphot/doc/psphot.txt	(revision 9533)
@@ -0,0 +1,160 @@
+
+TBD priorities:
+
+- test psImageSmooth / remove psImageSmooth_EAM
+- unify pmSourceLocalSky / pmSourceLocalSky_EAM
+- unify pmSourceMoments_EAM / pmSourceMoments_EAM
+- merge with image data hierarchy stuff
+- better name for psPSF_Test
+
+Defined APIs:
+
+top-level psphot functions : these do not need to be in psModules
+  psMetadata  *psphotArguments ();
+  psImageData *psphotSetup ();
+  psStats     *psphotImageStats ();
+  psArray     *pmPeaksSigmaLimit ();
+  psArray     *psphotSourceStats ();
+  pmPSF       *psphotChoosePSF ();
+  bool         psphotApplyPSF ();
+  bool         psphotFitGalaxies ();
+  void         psphotOutput ();
+
+psphot-specific functions
+  bool         psphotMarkPSF ();
+  bool         psphotSubtractPSF ();
+  int          psphotSortBySN ();
+  int          psphotSaveImage ();
+
+** add the following entries to the psLibSDRS
+
+psLib extra utilities
+  bool         psTimerStart ();		- already in SDRS
+  void         psTimerFree (); 		- already in SDRS
+  bool         psTimerClear ();		- add to psLibSDRS
+  psF64        psTimerMark (); 		- already in SDRS
+  psS32        psLogArguments ();	- now: psArgumentVerbosity in SDRS
+  psS32        psTraceArguments ();	- now: psArgumentVerbosity in SDRS
+  int          psArgumentGet ();	- already in SDRS
+  int          psArgumentRemove ();	- already in SDRS
+  psVector    *psVectorCreate ();	- add to psLibSDRS
+  int          psImageCountPixelMask ();- add to psLibSDRS
+  psVector    *psGetRowVectorFromImage(); - add to psLibSDRS?
+  bool         p_psVectorPrintRow ();     - add to psLibSDRS?
+
+// basic image functions
+  bool         psImageInit ();		- already in SDRS
+  void         psImageSmooth_EAM ();    - test psLib version and drop
+
+// psLine functions
+  psLine      *psLineAlloc ();		- add to psLibSDRS
+  bool         psLineInit (); 		- add to psLibSDRS
+  bool         psLineAdd ();  		- add to psLibSDRS
+
+** compare with current implementation / update psLib
+   we need to define a mechanism to carry the beta values and the
+   parameter limits
+
+// minimize 
+  psBool       p_psMinLM_GuessABP_EAM ();
+  psBool       psMinimizeLMChi2_EAM();
+  psF64        p_psMinLM_dLinear ();  -- not included in pslib.h files
+
+** already in psLib : some need to be cleaned up by MHPCC
+
+// polynomial functions
+  psF64           Polynomial2DEval();
+  psImage        *psBuildSums2D();
+  psPolynomial2D *VectorFitPolynomial2DOrd_EAM();
+  psPolynomial2D *Polynomial2DAlloc();
+  void            psPolynomial2DDump ();
+  psPolynomial2D *RobustFit2D_nomask();
+  psPolynomial2D *RobustFit2D();
+  psVector       *Polynomial2DEvalVector();
+
+  psVector       *psBuildSums1D();
+  void            psPolynomial1DDump ();
+  psF64           Polynomial1DEval_EAM();
+  psVector       *Polynomial1DEvalVector_EAM();
+  psPolynomial1D *Polynomial1DAlloc();
+  psPolynomial1D *VectorFitPolynomial1DOrd_EAM();
+
+** make sure these are all in the psModules SDRS:
+
+pmObjects.h:
+  pmMoments           *pmMomentsAlloc();
+  pmModel             *pmModelAlloc();
+  pmSource            *pmSourceAlloc();
+  psVector            *pmFindVectorPeaks()
+  psArray             *pmFindImagePeaks()
+  psList              *pmCullPeaks()
+  pmSource            *pmSourceLocalSky()
+  bool                 pmSourceMoments()
+  pmPSFClump           pmSourcePSFClump()
+  bool                 pmSourceRoughClass()
+  bool                 pmSourceSetPixelsCircle()
+  pmModel             *pmSourceModelGuess()
+  psArray             *pmSourceContour()
+  bool                 pmSourceFitModel()
+  bool                 pmSourceAddModel()
+  bool                 pmSourceSubModel()
+  int                  pmModelParameterCount ();
+  char                *pmModelGetType ();
+  pmModelType          pmModelSetType ();
+  pmModelFunc          pmModelFunc_GetFunction ();
+  pmModelFlux          pmModelFlux_GetFunction ();
+  pmModelRadius        pmModelRadius_GetFunction ();
+  pmModelLimits        pmModelLimits_GetFunction ();
+  pmModelGuessFunc     pmModelGuessFunc_GetFunction ();
+  pmModelFromPSFFunc   pmModelFromPSFFunc_GetFunction ();
+  pmModelFitStatusFunc pmModelFitStatusFunc_GetFunction ();
+
+model function abstractions:
+  typedef psMinimizeLMChi2Func pmModelFunc;
+  typedef psF64 (*pmModelFlux)(const psVector *params);
+  typedef psF64 (*pmModelRadius)(const psVector *params, double flux);
+  typedef bool (*pmModelLimits)(psVector **beta_lim, psVector **params_min, psVector **params_max);
+  typedef bool (*pmModelGuessFunc)(pmModel *model, pmSource *source);
+  typedef bool (*pmModelFromPSFFunc)(pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf);
+  typedef bool (*pmModelFitStatusFunc)(pmModel *model);
+
+conversions between elliptical shape representations
+  EllipseAxes  EllipseMomentsToAxes ();
+  EllipseShape EllipseAxesToShape ();
+  EllipseAxes  EllipseShapeToAxes ();
+
+output functions
+  bool         pmSourcesWriteText ();  	-- be careful about psImageData...
+  bool         pmSourcesWriteOBJ ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteCMP ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteCMF ();   	-- be careful about psImageData...
+  bool         pmSourcesWriteSX ();    	-- be careful about psImageData...
+  int          pmSourcesDophotType ();
+  bool         pmPeaksWriteText ();
+  bool         pmMomentsWriteText ();
+  bool         pmModelWritePSFs ();
+  bool         pmModelWriteFLTs ();
+  bool         pmModelWriteNULLs ();
+
+// psf utilities
+  pmPSF       *pmPSFAlloc ();
+  pmPSF_Test  *pmPSF_TestAlloc ();
+  pmPSF_Test  *pmPSF_TestModel ();
+  bool         pmPSFFromModels ();
+  pmModel     *pmModelFromPSF ();
+  bool         pmSourcePhotometry ();
+  bool         pmPSFMetricModel ();
+
+// psModule extra utilities
+  bool         pmSourceFitModel_EAM();  -- requires updated version of psMinimize...
+  bool         pmSourceLocalSky_EAM (); -- compare with pmSourceLocalSky & choose
+  bool         pmSourceMoments_EAM();   -- compare with pmSourceMoments & choose
+  bool         pmSourceDefinePixels();  -- be careful about psImageData...
+  bool         pmModelFitStatus ();
+  int          pmSourceDophotType ();
+  psF32        pmConfigLookupF32 ();	-- merge with Paul's image container hierarchy
+
+** this needs to be confronted with the phase2 / image container hierarchy
+
+// psImageData functions
+  psImageData *psImageDataAlloc ();
Index: /tags/RHL_v1_1/psphot/doc/regions.txt
===================================================================
--- /tags/RHL_v1_1/psphot/doc/regions.txt	(revision 9533)
+++ /tags/RHL_v1_1/psphot/doc/regions.txt	(revision 9533)
@@ -0,0 +1,159 @@
+
+I am having some trouble with functions that refer to both subimages
+and regions.  there are inconsistencies between the coordinate
+conventions.  I am going to go through the list of image functions are
+decide if we can choose either 1) external coordinates always refer to
+parent coordinates or 2) something else!
+
+bool psImageSet(const psImage *image, int x, int y, double complex value);
+double complex psImageGet(const psImage *image, int x, int y);
+ - sdrs does not specify
+ - code uses subimage
+
+psRegion psRegionForImage(psImage *image, psRegion in);
+ - sdrs says subimage
+ - code uses subimage
+
+psImage *psFitsReadImage(psImage *out, const psFits *fits, psRegion region, int z);
+ - region here specifies a subimage of the disk image
+ 
+bool psFitsUpdateImage(psFits *fits, const psImage *input, int x0, int y0, int z);
+ - x0,y0 here refer to the disk image coordinates
+
+bool psFitsWriteImage(psFits *fits, psMetadata *header, const psImage
+*input, int depth, const char *extname);
+ - note that the output disk image is truncated to the subimage
+
+bool psFitsInsertImage(psFits *fits, psMetadata *header, const psImage *input, int depth,
+                       const char *extname, bool after);
+ - same as above
+
+psImage *psImageSubset(psImage *image, psRegion region);
+ - code uses region as coordinates in parent image
+ - sdrs does not specify
+ - XXX I think the range tests for region fail if the input is a
+   subimage
+
+psImage *psImageCopy(psImage *output, const psImage *input, psElemType type);
+ - maintains the col0,row0 values
+
+psVector *psImageRow(psVector *out, const psImage *input, int row);
+psVector *psImageCol(psVector *out, const psImage *input, int column);
+ - code uses subimage coordinates for row and column
+
+psVector *psImageSlice()
+psVector* psImageCut()
+ - code uses subimage coordinates (no correction)
+ - sdrs does not specify
+
+psImage *psImageRebin()
+ - mask and input image must currently agree
+
+psImage *psImageTransform()
+ - sdrs is unclear: I think the region defines the output image such
+ that the pixel in the input image *parent* coordinates which
+ corresponds to region.x0,y0 in turn corresponds to the output image
+ 0,0 pixel; the output image always has col0,row0 = 0,0
+
+long psImageCountPixelMask (psImage *mask, psRegion region, psMaskType value);
+ - sdrs says the region corresponds to subimage coordinates
+
+psPolynomial2D *psImageFitPolynomial(psPolynomial2D *coeffs, const psImage *input);
+psImage *psImageEvalPolynomial(psImage *input, const psPolynomial2D *coeffs);
+ - sdrs does not specify
+ - polynomial coordinates should refer to the *parent* image
+
+double complex psImagePixelInterpolate(input, x, y, ...)
+ - sdrs does not specify
+
+int psImageOverlaySection(psImage *image, const psImage *overlay, )
+ - sdrs does not specify
+
+psImage *psPixelsToMask()
+psPixels *psPixelsFromMask()
+ - sdrs does not specify
+
+void psImageMaskRegion()
+void psImageKeepRegion()
+ - sdrs does not specify
+
+void psImageMaskCircle()
+void psImageKeepCircle()
+ - sdrs does not specify
+
+pmReadout *pmSubtractSky(pmReadout *in, psPolynomial2D *poly, psImage *mask, psU8 maskVal, 
+                         int binFactor, psStats *stats, float clipSD);
+ - choice of polynomial coordinates
+
+stats *pmFringeStats (psArray *fringePoints, psImage *image, psMetadata *config);
+ - unspecified
+
+psF32 pmModelEval(pmModel *model, psImage *image, psS32 col, psS32 row);
+ - sdrs says col,row are in *subimage* coords
+
+psArray *pmFindImagePeaks(const psImage *image, float threshold);
+ - does not specify coordinate system
+ - code uses subimage?
+
+bool pmSourceDefinePixels()
+bool pmSourceRedefinePixels()
+ - sdrs does not specify very clearly
+
+
+
+---------
+
+functions which are not affected
+psFitsReadImage()
+psFitsUpdateImage()
+psFitsWriteImage()
+psFitsInsertImage()
+psImageCopy()  -- maintains col0,row0
+psImageRebin() -- mask and image must match
+
+functions which currently use parent coordinates (CORRECT)
+psImageSubset()  -- check on range tests
+psImageMaskRegion()
+psImageKeepRegion()
+psImageMaskCircle()
+psImageKeepCircle()
+pmSourceMoments -- (OK if input peaks are in parent coords)
+
+functions which currently use subimage coordinates (FIX)
+psImageSet()
+psImageGet()
+psRegionForImage() *fixed*
+psImageRow()
+psImageCol()
+psImageSlice()
+psImageCut()
+psImageCountPixelMask ();
+pmModelEval()
+pmFindImagePeaks() *fixed*
+pmSourceDefinePixels() *fixed by psRegionForImage fix*
+pmSourceRedefinePixels() *fixed by psRegionForImage fix*
+
+functions which are unclear (CHECK)
+psImageTransform()
+psImageFitPolynomial()
+psImageEvalPolynomial()
+psImagePixelInterpolate()
+psPixelsToMask()
+pmSubtractSky()
+pmFringeStats()
+
+
+***** one ambiguity: if we have a subimage, but we do not have the
+      parent image data (subimage does not carry dimensions of parent
+      array), then a 0 or negative upper-edge of a region cannot be
+      defined relative to the parent. we can specify it relative to
+      the subimage.  so, for example, given a subimage [32,100:32,100]
+      of a much larger image, then psRegionForImage with 0,0,0,0 as
+      input would return a region with values 32,100:32,100...
+
+
+
+
+
+*** things I've modified
+    psRegionForImage expects parent (not subimage) coordinates
Index: /tags/RHL_v1_1/psphot/doc/speed.txt
===================================================================
--- /tags/RHL_v1_1/psphot/doc/speed.txt	(revision 9533)
+++ /tags/RHL_v1_1/psphot/doc/speed.txt	(revision 9533)
@@ -0,0 +1,129 @@
+
+psphot -g
+psModule -g
+psLib -O2
+on alala:
+
+alala: psphot -vv starfield.fits starfield starfield.cnf -resid starfield.resid.3.fits
+    load data: 0.983568 sec
+    System random seed value used  seed = 25904F7C7454AE25 hex
+    background: 1229.280029 +/- 27.700180
+    image stats: 0.063085 sec
+    System random seed value used  seed = 411E5A0600A49AF3 hex
+    fit background model: 0.580152 sec
+    smooth: 1.842415 sec
+    threshold: 83.100540 DN
+    4356 peaks: 0.571587 sec
+    4230 moments: 3.965099 sec
+    SN range: 3.355829 - 1043.401001
+    identified 107 blended objects (0.121737 sec)
+    selected candidate 300 PSF objects
+    fit flt:   3.682850 sec for 300 sources
+    fit psf:   1.704141 sec for 300 sources
+    fit stats: 0.000884 +/- 0.004499
+    try model PS_MODEL_QGAUSS, ap-fit: -0.007532 +/- 0.004499 : sky bias: -0.717999
+    select psf model: 5.402742 sec
+    selected psf model PS_MODEL_QGAUSS, ApResid: -0.007532 +/- 0.004499
+    fitting pixels with at least 27.700180 object counts
+    built models: 0.991345 (4230 objects)
+    built matrix: 1.472869 (27998 elements)
+    measure ensemble of PSFs: 4.334556
+    fit PSF models: 12.205738 sec for 4230 objects
+    replace unfitted models: 0.021473 sec (4230 objects)
+    measure aperture residuals : 0.451886 sec
+    measure full-frame aperture residual: 0.454032 sec
+    aperture residual: -0.002842 +/- 0.002729 : 0.166072 bias
+    wrote output: 4.648821 sec
+    complete psphot run: 35.334080 sec
+
+
+psphot -g
+psModule -O2
+psLib -O2
+on alala:
+
+alala: psphot -vv starfield.fits starfield starfield.cnf -resid starfield.resid.3.fits
+    load data: 1.001502 sec
+    System random seed value used  seed = 5F43A63F532D4B3F hex
+    background: 1229.280029 +/- 27.700180
+    image stats: 0.063169 sec
+    System random seed value used  seed = F5F5FF99A6578F61 hex
+    fit background model: 0.530384 sec
+    smooth: 1.856414 sec
+    threshold: 83.100540 DN
+    4365 peaks: 0.237292 sec
+    4239 moments: 4.087611 sec
+    SN range: 3.356469 - 1043.400879
+    identified 107 blended objects (0.123372 sec)
+    selected candidate 300 PSF objects
+    fit flt:   3.741444 sec for 300 sources
+    fit psf:   1.676478 sec for 300 sources
+    fit stats: 0.000883 +/- 0.004499
+    try model PS_MODEL_QGAUSS, ap-fit: -0.007532 +/- 0.004499 : sky bias: -0.717985
+    select psf model: 5.458156 sec
+    selected psf model PS_MODEL_QGAUSS, ApResid: -0.007532 +/- 0.004499
+    fitting pixels with at least 27.700180 object counts
+    built models: 0.791613 (4239 objects)
+    built matrix: 1.267909 (28101 elements)
+    measure ensemble of PSFs: 3.551364
+    fit PSF models: 11.240197 sec for 4239 objects
+    replace unfitted models: 0.018051 sec (4239 objects)
+    measure aperture residuals : 0.411856 sec
+    measure full-frame aperture residual: 0.413843 sec
+    aperture residual: -0.002831 +/- 0.002717 : 0.164772 bias
+    wrote output: 4.355429 sec
+    complete psphot run: 33.126283 sec
+
+after reducing indirection in pmSourceMoments:
+    4268 moments: 3.762452 sec
+
+psphot -O2
+psModule -O2
+psLib -O2
+
+alala: psphot -vv starfield.fits starfield starfield.cnf -resid starfield.resid.3.fits
+    load data: 0.484324 sec
+    System random seed value used  seed = 980050732A392EC7 hex
+    background: 1229.280029 +/- 27.700180
+    image stats: 0.058759 sec
+    System random seed value used  seed = 44E1A6EE77B0E9CE hex
+    fit background model: 0.395125 sec
+    smooth: 1.861776 sec
+    threshold: 83.100540 DN
+    4351 peaks: 0.234874 sec
+    4264 moments: 3.719435 sec
+    SN range: 2.023345 - 1043.401123
+    identified 124 blended objects (0.115230 sec)
+    selected candidate 300 PSF objects
+    fit flt:   3.525501 sec for 300 sources
+    fit psf:   1.595031 sec for 300 sources
+    fit stats: 0.000884 +/- 0.004499
+    try model PS_MODEL_QGAUSS, ap-fit: -0.007532 +/- 0.004499 : sky bias: -0.717976
+    select psf model: 5.160442 sec
+    selected psf model PS_MODEL_QGAUSS, ApResid: -0.007532 +/- 0.004499
+    fitting pixels with at least 27.700180 object counts
+    built models: 0.760805 (4264 objects)
+    built matrix: 0.908674 (23703 elements)
+    measure ensemble of PSFs: 3.115425
+    fit PSF models: 10.162397 sec for 4264 objects
+    replace unfitted models: 0.017491 sec (4264 objects)
+    measure aperture residuals : 0.408242 sec
+    measure full-frame aperture residual: 0.410207 sec
+    aperture residual: -0.002908 +/- 0.002734 : 0.170692 bias
+    wrote output: 4.225560 sec
+    complete psphot run: 30.149281 sec
+
+
+    fit PSF models: 10.162397 sec for 4264 objects
+    select psf model: 5.160442 sec
+    wrote output: 4.225560 sec
+    4264 moments: 3.719435 sec
+    measure ensemble of PSFs: 3.115425 sec
+
+    other : 3.5 sec
+
+    fixed per objects : 4 msec / object (17 of 30 sec)
+
+
+** still can re-compile with -DPS_NO_TRACE
+** ASSERTS cannot be compiled out at the moment!
Index: /tags/RHL_v1_1/psphot/doc/versions.txt
===================================================================
--- /tags/RHL_v1_1/psphot/doc/versions.txt	(revision 9533)
+++ /tags/RHL_v1_1/psphot/doc/versions.txt	(revision 9533)
@@ -0,0 +1,49 @@
+
+2006.02.02
+  I'm tagging psphot after I updated it to work with the readouts
+  using the structures being used for ppImage.  In this release, I
+  have also moved to autoconf for psphot, and the psphotReadout
+  portion is being defined as a library against which the other
+  elements are linked.  It should be possible to use this library with
+  ppImage, but I have not yet tested that.  
+
+  link psphot_dev_08 against psLib:eam_rel9_b2 and
+  psModule:eam_rel9_b2.
+
+2006.01.18
+  the tag below (psphot_dev_07) was moved to a later release.  It
+  should be used with psLib:eam_rel9_b1 from 2006/1/18 and
+  psModule:eam_rel9_p0.  
+
+2005.12.23
+  psphot_dev_07 should be compiled against psLib:eam_rel9_b1 and
+  psModule:eam_rel9_b1.  This version now handles the ensemble PSF
+  fitting, detection of blended sources, fitting of 'blends' (groups
+  of PSFs with distinct be 'blended' peaks) and 'blobs' (unresolved
+  extended objects, with a test for double PSF vs extended source
+  model.
+
+2005.11.25
+
+  psphot_dev_05 should be compiled against psLib tag eam_rel8_b2 and
+  psModules tag eam_rel8_b1.  this version removes all _EAM versions
+  of psLib and psModules code.  It now uses the psModules version of
+  the object code.
+
+  psphot_dev_04 should be compiled against psLib tag eam_rel8_b2 this
+  version removes all _EAM versions of psLib code, so that it is now
+  consistent with psLib.v8 (fixes are pushed into the eam_rel8_b2
+  branch).
+
+2005.11.16
+
+  psphot_dev_03 should be compiled against psLib tag eam_rel8_b1, a
+  branch starting from psLib rel8_0.  I needed to make some minor
+  fixes in psLib to work successfully with psLib rel8_0.
+
+  psphot_dev_02 was getting segfaults from a bug added into psLib in
+  rel8_0 in p_psVectorClippedStats.  I also fixed some errors arising
+  from changing types (argument to psMetadataParseConfig).
+
+
+
Index: /tags/RHL_v1_1/psphot/psphot.pc.in
===================================================================
--- /tags/RHL_v1_1/psphot/psphot.pc.in	(revision 9533)
+++ /tags/RHL_v1_1/psphot/psphot.pc.in	(revision 9533)
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libpsphot
+Description: Pan-STARRS Photometry Library
+Version: @VERSION@
+Libs: -L${libdir} -lpsphot @PSPHOT_LIBS@
+Cflags: -I${includedir} @PSPHOT_CFLAGS@
Index: /tags/RHL_v1_1/psphot/src/.cvsignore
===================================================================
--- /tags/RHL_v1_1/psphot/src/.cvsignore	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/.cvsignore	(revision 9533)
@@ -0,0 +1,15 @@
+*.o
+*.lo
+.libs
+.deps
+Makefile
+Makefile.in
+libpsphot.la
+psphot
+psphotTest
+psphot.loT
+psphotErrorCodes.h
+psphotErrorCodes.c
+config.h
+config.h.in
+stamp-h1
Index: /tags/RHL_v1_1/psphot/src/Makefile.am
===================================================================
--- /tags/RHL_v1_1/psphot/src/Makefile.am	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/Makefile.am	(revision 9533)
@@ -0,0 +1,68 @@
+
+lib_LTLIBRARIES = libpsphot.la
+libpsphot_la_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PSPHOT_CFLAGS)
+
+bin_PROGRAMS = psphot
+psphot_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PSPHOT_CFLAGS)
+psphot_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS)
+psphot_LDADD = libpsphot.la
+
+psphot_SOURCES = \
+	psphot.c		
+
+libpsphot_la_SOURCES = \
+	psphotErrorCodes.c	\
+	psphotVersion.c		\
+	psphotModelGroupInit.c	\
+	psphotArguments.c	\
+	psphotParseCamera.c	\
+	psphotImageLoop.c	\
+	psphotMaskReadout.c	\
+	psphotReadout.c		\
+	psphotImageMedian.c	\
+	psphotFindPeaks.c	\
+	psphotSourceStats.c	\
+	psphotRoughClass.c	\
+	psphotBasicDeblend.c	\
+	psphotChoosePSF.c	\
+	psphotEnsemblePSF.c	\
+	psphotBlendFit.c	\
+	psphotReplaceUnfit.c	\
+	psphotApResid.c		\
+	psphotMagnitudes.c	\
+	psphotSkyReplace.c	\
+	psphotEvalPSF.c		\
+	psphotEvalFLT.c		\
+	psphotSourceFits.c	\
+	psphotRadiusChecks.c	\
+	psphotSortBySN.c	\
+	psphotOutput.c		\
+	psphotGrowthCurve.c	\
+	psphotFakeSources.c	\
+	psphotModelTest.c	\
+	psphotFitSet.c		\
+	psphotWeightBias.c	\
+	psphotSourceFreePixels.c \
+	psphotCleanup.c	   	
+
+include_HEADERS = \
+	psphot.h \
+	psphotErrorCodes.h
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+#
+# Error codes.
+#
+BUILT_SOURCES = psphotErrorCodes.h psphotErrorCodes.c
+CLEANFILES = psphotErrorCodes.h psphotErrorCodes.c
+
+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: /tags/RHL_v1_1/psphot/src/models/pmModel_GAUSS.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/models/pmModel_GAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/models/pmModel_GAUSS.c	(revision 9533)
@@ -0,0 +1,152 @@
+
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+*****************************************************************************/
+
+psF32 pmModelFunc_GAUSS(psVector *deriv,
+			const psVector *params,
+			const psVector *x)
+{
+    psF32 X  = x->data.F32[0] - params->data.F32[2];
+    psF32 Y  = x->data.F32[1] - params->data.F32[3];
+    psF32 px = params->data.F32[4]*X;
+    psF32 py = params->data.F32[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + params->data.F32[6]*X*Y;
+    psF32 r  = exp(-z);
+    psF32 q  = params->data.F32[1]*r;
+    psF32 f  = q + params->data.F32[0];
+
+    if (deriv != NULL) {
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2*px*params->data.F32[4] + params->data.F32[6]*Y);
+        deriv->data.F32[3] = q*(2*py*params->data.F32[5] + params->data.F32[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+    }
+    return(f);
+}
+
+psF64 pmModelFlux_GAUSS(const psVector *params)
+{
+    psF64 A1   = PS_SQR(params->data.F32[4]);
+    psF64 A2   = PS_SQR(params->data.F32[5]);
+    psF64 A3   = PS_SQR(params->data.F32[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    psF64 Flux = params->data.F32[1] * Area;
+
+    return(Flux);
+}
+
+// return the radius which yields the requested flux
+psF64 pmModelRadius_GAUSS  (const psVector *params, psF64 flux)
+{
+    
+    if (flux <= 0) return (1.0);
+    if (params->data.F32[1] <= 0) return (1.0);
+    if (flux >= params->data.F32[1]) return (1.0);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    return (radius);
+}
+
+// define the parameter limits
+bool pmModelLimits_GAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max) {
+
+    *beta_lim   = psVectorAlloc (7, PS_TYPE_F32);
+    *params_min = psVectorAlloc (7, PS_TYPE_F32);
+    *params_max = psVectorAlloc (7, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +5.0;
+
+    return (TRUE);
+}
+
+// make an initial guess for parameters
+bool pmModelGuess_GAUSS (pmModel *model, pmSource *source) {
+
+    pmMoments *moments = source->moments;
+    psF32     *params  = model->params->data.F32;
+
+    params[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = moments->x;
+    params[3] = moments->y;
+    params[4] = 1.2 / moments->Sx;
+    params[5] = 1.2 / moments->Sy;
+    params[6] = 0.0;
+    return(true);
+}
+
+// construct the PSF model from the FLT model and the psf
+bool pmModelFromPSF_GAUSS (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf) {
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+    
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 7; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = psPolynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+// check the status of the fitted model
+bool pmModelFitStatus_GAUSS (pmModel *model) {
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[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;
+}
Index: /tags/RHL_v1_1/psphot/src/models/pmModel_PGAUSS.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/models/pmModel_PGAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/models/pmModel_PGAUSS.c	(revision 9533)
@@ -0,0 +1,167 @@
+
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+*****************************************************************************/
+
+psF32 pmModelFunc_PGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[PM_PAR_XPOS];
+    psF32 Y  = x->data.F32[1] - PAR[PM_PAR_YPOS];
+    psF32 px = PAR[PM_PAR_SXX]*X;
+    psF32 py = PAR[PM_PAR_SYY]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*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) {
+        // note difference from a pure gaussian: q = PAR[PM_PAR_I0]*r
+        psF32 q = PAR[PM_PAR_I0]*r*r*t;
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[PM_PAR_SXX] + PAR[PM_PAR_SXY]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[PM_PAR_SYY] + PAR[PM_PAR_SXY]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+    }
+    return(f);
+}
+
+bool pmModelLimits_PGAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max) {
+
+    *beta_lim   = psVectorAlloc (7, PS_TYPE_F32);
+    *params_min = psVectorAlloc (7, PS_TYPE_F32);
+    *params_max = psVectorAlloc (7, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +5.0;
+
+    return (TRUE);
+}
+
+psF64 pmModelFlux_PGAUSS(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[PM_PAR_SXX]);
+    psF64 A2   = PS_SQR(PAR[PM_PAR_SYY]);
+    psF64 A3   = PS_SQR(PAR[PM_PAR_SXY]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+	f = 1.0 / (1 + z + z*z/2 + z*z*z/6);
+	norm += f;
+    }
+    norm *= 0.01;
+    
+    psF64 Flux = params->data.F32[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 pmModelRadius_PGAUSS  (const psVector *params, psF64 flux)
+{
+    if (flux <= 0) return (1.0);
+    if (params->data.F32[1] <= 0) return (1.0);
+    if (flux >= params->data.F32[1]) return (1.0);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    if (isnan(radius)) {
+      fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool pmModelGuess_PGAUSS (pmModel *model, pmSource *source) {
+
+    pmMoments *moments = source->moments;
+    psF32     *params  = model->params->data.F32;
+
+    params[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = moments->x;
+    params[3] = moments->y;
+    params[4] = 1.2 / moments->Sx;
+    params[5] = 1.2 / moments->Sy;
+    params[6] = 0.0;
+    return(true);
+}
+
+bool pmModelFromPSF_PGAUSS (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf) {
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+    
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 7; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = psPolynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool pmModelFitStatus_PGAUSS (pmModel *model) {
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[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;
+}
Index: /tags/RHL_v1_1/psphot/src/models/pmModel_QGAUSS.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/models/pmModel_QGAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/models/pmModel_QGAUSS.c	(revision 9533)
@@ -0,0 +1,197 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z^M + z^N)
+
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = 
+    params->data.F32[8] = 
+*****************************************************************************/
+
+/* XXX EAM : we need a way to have user-set values for fix parameters */
+# define QG_S1  2.5
+# define dQG_S1 1.5
+/* dQG_S1 is QG_S1 - 1.0 */
+
+psF32 pmModelFunc_QGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[PM_PAR_XPOS];
+    psF32 Y  = x->data.F32[1] - PAR[PM_PAR_YPOS];
+    psF32 px = PAR[PM_PAR_SXX]*X;
+    psF32 py = PAR[PM_PAR_SYY]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[PM_PAR_SXY]*X*Y;
+
+    psF32 r  = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, QG_S1));
+    psF32 f  = PAR[PM_PAR_I0]*r + PAR[PM_PAR_SKY];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[PM_PAR_I0]*r*r;
+	psF32 q = t*(PAR[PM_PAR_7] + QG_S1*pow(z, dQG_S1));
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[PM_PAR_SXX] + PAR[PM_PAR_SXY]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[PM_PAR_SYY] + PAR[PM_PAR_SXY]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -t*z;
+    }
+    return(f);
+}
+
+bool pmModelLimits_QGAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max) {
+
+    *beta_lim   = psVectorAlloc (8, PS_TYPE_F32);
+    *params_min = psVectorAlloc (8, PS_TYPE_F32);
+    *params_max = psVectorAlloc (8, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+    beta_lim[0][0].data.F32[7] = 0.5;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+    params_min[0][0].data.F32[7] = 0.1;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +5.0;
+    params_max[0][0].data.F32[7] = 10.0;
+
+    return (TRUE);
+}
+
+bool pmModelGuess_QGAUSS (pmModel *model, pmSource *source) {
+
+    pmMoments *moments = source->moments;
+    pmPeak    *peak    = source->peak;
+    psF32     *params  = model->params->data.F32;
+
+    params[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->y;
+    params[4] = 1.2 / moments->Sx;
+    params[5] = 1.2 / moments->Sy;
+    params[6] = 0.0;
+    params[7] = 1.0;
+    return(true);
+}
+
+psF64 pmModelFlux_QGAUSS(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[PM_PAR_SXX]);
+    psF64 A2   = PS_SQR(PAR[PM_PAR_SYY]);
+    psF64 A3   = PS_SQR(PAR[PM_PAR_SXY]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+	f = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, QG_S1));
+	norm += f;
+    }
+    norm *= 0.01;
+    
+    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 pmModelRadius_QGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 z, f;
+    psF32 *PAR = params->data.F32;
+
+    if (flux <= 0) return (1.0);
+    if (PAR[PM_PAR_I0] <= 0) return (1.0);
+    if (flux >= PAR[PM_PAR_I0]) return (1.0);
+
+    // if Sx == Sy, sigma = Sx == Sy
+    psF64 sigma = hypot (1.0 / PAR[PM_PAR_SXX], 1.0 / PAR[PM_PAR_SYY]) / sqrt(2.0);
+    psF64 dz = 1.0 / (2.0 * sigma*sigma);
+    psF64 limit = flux / PAR[PM_PAR_I0];
+
+    // we can do this much better with intelligent choices here
+    for (z = 0.0; z < 20.0; z += dz) {
+	f = 1.0 / (1 + PAR[PM_PAR_7]*z + pow(z, QG_S1));
+	if (f < limit) break;
+    }
+    psF64 radius = sigma * sqrt (2.0 * z);
+    if (isnan(radius)) {
+      fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool pmModelFromPSF_QGAUSS (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf) {
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+    
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 8; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = psPolynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool pmModelFitStatus_QGAUSS (pmModel *model) {
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[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;
+}
Index: /tags/RHL_v1_1/psphot/src/models/pmModel_RGAUSS.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/models/pmModel_RGAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/models/pmModel_RGAUSS.c	(revision 9533)
@@ -0,0 +1,151 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z + z^Npow)
+
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = 1 / SigmaX;
+    params->data.F32[5] = 1 / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = Npow
+*****************************************************************************/
+
+psF64 psModelFunc_RGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[PM_PAR_XPOS];
+    psF32 Y  = x->data.F32[1] - PAR[PM_PAR_YPOS];
+    psF32 px = PAR[PM_PAR_SXX]*X;
+    psF32 py = PAR[PM_PAR_SYY]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[PM_PAR_SXY]*X*Y;
+
+    // psF32 FACT = 1 + 5*exp(-5*PAR[7]);
+    
+    psF32 p  = pow(z, PAR[PM_PAR_7] - 1.0);
+    psF32 r  = 1.0 / (1 + z + z*p);
+    psF32 f  = PAR[PM_PAR_I0]*r + PAR[PM_PAR_SKY];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[PM_PAR_I0]*r*r;
+	psF32 q = t*(1 + PAR[PM_PAR_7]*p);
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[PM_PAR_SXX] + PAR[PM_PAR_SXY]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[PM_PAR_SYY] + PAR[PM_PAR_SXY]*X);
+        deriv->data.F32[4] = -q*px*X*2;
+        deriv->data.F32[5] = -q*py*Y*2;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -5.0*t*log(z)*p*z;
+
+        // deriv->data.F32[4] = -1.8*q*px*X*2;
+        // deriv->data.F32[5] = -1.8*q*py*Y*2;
+        // deriv->data.F32[6] = -1.5*q*X*Y;
+        // deriv->data.F32[7] = -5.0*t*log(z)*p*z;
+    }
+    return(f);
+}
+
+psF64 psModelFlux_RGAUSS(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[PM_PAR_SXX]);
+    psF64 A2   = PS_SQR(PAR[PM_PAR_SYY]);
+    psF64 A3   = PS_SQR(PAR[PM_PAR_SXY]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+	f = 1.0 / (1 + z + pow(z, PAR[PM_PAR_7]));
+	norm += f;
+    }
+    norm *= 0.01;
+    
+    psF64 Flux = params->data.F32[1] * Area * norm;
+
+    return(Flux);
+}
+
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 psModelRadius_RGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 z, f, p;
+    psF32 *PAR = params->data.F32;
+
+    if (flux <= 0) return (1.0);
+    if (PAR[PM_PAR_I0] <= 0) return (1.0);
+    if (flux >= PAR[PM_PAR_I0]) return (1.0);
+
+    // if Sx == Sy, sigma = Sx == Sy
+    psF64 sigma = hypot (1.0 / PAR[PM_PAR_SXX], 1.0 / PAR[PM_PAR_SYY]) / sqrt(2.0);
+    psF64 dz = 1.0 / (2.0 * sigma*sigma);
+    psF64 limit = flux / PAR[PM_PAR_I0];
+
+    // we can do this much better with intelligent choices here
+    for (z = 0.0; z < 20.0; z += dz) {
+	p = pow(z, PAR[PM_PAR_7]);
+	f = 1.0 / (1 + z + p);
+	if (f < limit) break;
+    }
+    psF64 radius = sigma * sqrt (2.0 * z);
+    if (isnan(radius)) {
+      fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool psModelGuess_RGAUSS (psModel *model, psSource *source) {
+
+    psVector *params = model->params;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+    EllipseMoments moments;
+    
+    moments.x2 = PS_SQR(source->moments->Sx);
+    moments.y2 = PS_SQR(source->moments->Sy);
+    moments.xy = source->moments->Sxy;
+
+    axes = EllipseMomentsToAxes(moments);
+    shape = EllipseAxesToShape(axes);
+
+    params->data.F32[0] = source->moments->Sky;
+    params->data.F32[1] = source->peak->counts - source->moments->Sky;
+    params->data.F32[2] = source->moments->x;
+    params->data.F32[3] = source->moments->y;
+    params->data.F32[4] = 1.0 / shape.sx;
+    params->data.F32[5] = 1.0 / shape.sy;
+    params->data.F32[6] = shape.sxy;
+    params->data.F32[7] = 2.0;
+    return(true);
+}
+
+bool psModelFromPSF_RGAUSS (psModel *modelPSF, psModel *modelFLT, pmPSF *psf) {
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+    
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 8; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = Polynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
Index: /tags/RHL_v1_1/psphot/src/models/pmModel_SGAUSS.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/models/pmModel_SGAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/models/pmModel_SGAUSS.c	(revision 9533)
@@ -0,0 +1,342 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z^Npow + St*z^Ntide)
+
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = 1 / SigmaX;
+    params->data.F32[5] = 1 / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = Npow
+    params->data.F32[8] = St
+*****************************************************************************/
+
+# define SQ(A)((A)*(A))
+psF64 psImageEllipseContour (EllipseAxes axes, double xc, double yc, psImage *image);
+psF64 p_psImageGetElementF64(psImage *a, int i, int j);
+
+psF32 pmModelFunc_SGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[PM_PAR_XPOS];
+    psF32 Y  = x->data.F32[1] - PAR[PM_PAR_YPOS];
+    psF32 px = PAR[PM_PAR_SXX]*X;
+    psF32 py = PAR[PM_PAR_SYY]*Y;
+    psF32 z  = PS_MAX((0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[PM_PAR_SXY]*X*Y), 1e-8);
+    // note that if z -> 0, dPAR[PM_PAR_7] -> -inf
+    // also z^(PAR[PM_PAR_7]-1) -> Inf 
+
+    psF32 pr = z*PAR[PM_PAR_8];
+    psF32 pr3 = pr*pr*pr;
+    psF32 p  = pow(z, PAR[PM_PAR_7] - 1.0);
+    psF32 r  = 1.0 / (1 + z*p + pr*pr3);
+    psF32 f  = PAR[PM_PAR_I0]*r + PAR[PM_PAR_SKY];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[PM_PAR_I0]*r*r;
+	psF32 q = t*(PAR[PM_PAR_7]*p + 4*PAR[PM_PAR_8]*pr3);
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[PM_PAR_SXX] + PAR[PM_PAR_SXY]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[PM_PAR_SYY] + PAR[PM_PAR_SXY]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -2*t*log(z)*z*p;
+        deriv->data.F32[8] = -2*t*4*z*pr3;
+    }
+    return(f);
+}
+
+bool pmModelLimits_SGAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max) {
+
+    *beta_lim   = psVectorAlloc (9, PS_TYPE_F32);
+    *params_min = psVectorAlloc (9, PS_TYPE_F32);
+    *params_max = psVectorAlloc (9, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+    beta_lim[0][0].data.F32[7] = 0.5;
+    beta_lim[0][0].data.F32[8] = 0.05;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+    params_min[0][0].data.F32[7] = 0.5;
+    params_min[0][0].data.F32[8] = 0.05;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +3.0;
+    params_max[0][0].data.F32[7] = 5.0;
+    params_max[0][0].data.F32[8] = 0.5;
+
+    return (TRUE);
+}
+
+// measure the flux for the elliptical contour
+psF64 psImageEllipseContour (EllipseAxes axes, double xc, double yc, psImage *image) {
+
+    double t, dt, ct, st, xo, yo, value;
+    int N, Nt, x, y;
+
+    // choose dt to uniformly divide contour, with ~1 pix spacing at most
+    dt = asin (1 / axes.minor);
+    Nt = (int)(2*M_PI / dt) + 1;
+    dt = 2*M_PI / Nt;
+
+    ct = cos(axes.theta);
+    st = sin(axes.theta);
+    xo = xc - image->col0;
+    yo = yc - image->row0;
+    
+    psVector *contour = psVectorAlloc (Nt, PS_TYPE_F32);
+    for (t = 0, N = 0; (t < 2*M_PI) && (N < Nt); t += dt) {
+	x = ct*axes.major*cos(t) + st*axes.minor*sin(t) + xo;
+	y = ct*axes.minor*sin(t) + st*axes.major*cos(t) + yo;
+	value = p_psImageGetElementF64(image, x, y);
+	if (isfinite(value)) {
+	    contour->data.F32[N] = value;
+	    N++;
+	}
+    }
+    contour->n = N;
+    // accept every pixel: double counting is not so problematic here...
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
+    psVectorStats (stats, contour, NULL, NULL, 0);
+    value = stats->sampleMedian;
+
+    psFree (stats);
+    psFree (contour);
+
+    return (value);
+}
+    
+bool pmModelGuess_SGAUSS (pmModel *model, pmSource *source) {
+
+    pmMoments *sMoments = source->moments;
+    pmPeak    *peak     = source->peak;
+    psF32     *params   = model->params->data.F32;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+    EllipseMoments moments;
+    
+    moments.x2 = PS_SQR(sMoments->Sx);
+    moments.y2 = PS_SQR(sMoments->Sy);
+    moments.xy = sMoments->Sxy;
+
+    // solve the math to go from Moments To Shape
+    axes = EllipseMomentsToAxes(moments);
+    shape = EllipseAxesToShape(axes);
+
+    params[0] = sMoments->Sky;
+    params[1] = sMoments->Peak - sMoments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->y;
+    params[4] = 1.0 / shape.sx;
+    params[5] = 1.0 / shape.sy;
+    params[6] = shape.sxy;
+
+# if (0)    
+    f1 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+    axes.major *= 2.0;
+    axes.minor *= 2.0;
+    f2 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+
+    if (f1 > f2) {
+	params[7] = PS_MIN (3.0, PS_MAX (0.5, log(2.0*(f1/f2) - 1.0) / log(2.0)));
+    } else {
+	params[7] = 0.5;
+    }
+# endif
+
+    params[7] = 1.8;
+    params[8] = 0.1;
+
+
+    return(true);
+}
+
+// XXX EAM : test version using flux contours to guess slope
+bool pmModelGuess_SGAUSS_HARD (pmModel *model, pmSource *source) {
+
+    pmMoments *sMoments = source->moments;
+    pmPeak    *peak     = source->peak;
+    psF32     *params   = model->params->data.F32;
+    float f1, f2;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+    EllipseMoments moments;
+    
+    moments.x2 = PS_SQR(sMoments->Sx);
+    moments.y2 = PS_SQR(sMoments->Sy);
+    moments.xy = sMoments->Sxy;
+
+    // solve the math to go from Moments To Shape
+    axes = EllipseMomentsToAxes(moments);
+    shape = EllipseAxesToShape(axes);
+
+    params[0] = sMoments->Sky;
+    params[1] = sMoments->Peak - sMoments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->y;
+    params[4] = 1.0 / shape.sx;
+    params[5] = 1.0 / shape.sy;
+    params[6] = shape.sxy;
+
+    f1 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+    axes.major *= 2.0;
+    axes.minor *= 2.0;
+    f2 = psImageEllipseContour (axes, peak->x, peak->y, source->pixels);
+
+    if (f1 > f2) {
+	params[7] = PS_MIN (3.0, PS_MAX (0.5, log(2.0*(f1/f2) - 1.0) / log(2.0)));
+    } else {
+	params[7] = 0.5;
+    }
+    params[8] = 0.1;
+
+    return(true);
+}
+
+psF64 pmModelFlux_SGAUSS(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[PM_PAR_SXX]);
+    psF64 A2   = PS_SQR(PAR[PM_PAR_SYY]);
+    psF64 A3   = PS_SQR(PAR[PM_PAR_SXY]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+      psF32 pr = PAR[PM_PAR_8]*z;
+	f = 1.0 / (1 + pow(z, PAR[PM_PAR_7]) + SQ(SQ(pr)));
+	norm += f;
+    }
+    norm *= 0.01;
+    
+    psF64 Flux = PAR[PM_PAR_I0] * Area * norm;
+
+    return(Flux);
+}
+
+// XXX need to define the radius along the major axis
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 pmModelRadius_SGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 r, z, pr, f;
+    psF32 *PAR = params->data.F32;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+
+    if (flux <= 0) return (1.0);
+    if (PAR[PM_PAR_I0] <= 0) return (1.0);
+    if (flux >= PAR[PM_PAR_I0]) return (1.0);
+
+    // convert Sx,Sy,Sxy to major/minor axes
+    shape.sx = 1.0 / PAR[PM_PAR_SXX];
+    shape.sy = 1.0 / PAR[PM_PAR_SYY];
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    axes = EllipseShapeToAxes (shape);
+    psF64 dr = 1.0 / axes.major;
+    psF64 limit = flux / PAR[PM_PAR_I0];
+
+    // XXX : we can do this faster with an intelligent starting choice
+    for (r = 0.0; r < 20.0; r += dr) {
+	z = SQ(r);
+	pr = PAR[PM_PAR_8]*z;
+	f = 1.0 / (1 + pow(z, PAR[PM_PAR_7]) + SQ(SQ(pr)));
+	if (f < limit) break;
+    }
+    psF64 radius = 2.0 * axes.major * sqrt (z);
+    if (isnan(radius)) {
+	fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool pmModelFromPSF_SGAUSS (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf) {
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+    
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 9; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = psPolynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool pmModelFitStatus_SGAUSS (pmModel *model) {
+
+    psF32 dP;
+    bool  status;
+    EllipseAxes axes;
+    EllipseShape shape;
+  
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    shape.sx = 1.0 / PAR[PM_PAR_SXX];
+    shape.sy = 1.0 / PAR[PM_PAR_SYY];
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    axes = EllipseShapeToAxes (shape);
+
+    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 += PS_SQR(dPAR[PM_PAR_7] / PAR[PM_PAR_7]);
+    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);
+    status &= (fabs(PAR[PM_PAR_8]) < 0.5);
+    status &= (dPAR[PM_PAR_8] < 0.1);
+    status &= (axes.major > 1.41);
+    status &= (axes.minor > 1.41);
+    status &= ((axes.major / axes.minor) < 5.0);
+    status &= (PAR[PM_PAR_7] > 0.5);
+
+    if (status) return true;
+    return false;
+}
Index: /tags/RHL_v1_1/psphot/src/models/pmModel_TAUSS.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/models/pmModel_TAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/models/pmModel_TAUSS.c	(revision 9533)
@@ -0,0 +1,163 @@
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+*****************************************************************************/
+
+psF32 pmModelFunc_TAUSS(psVector *deriv,
+                        const psVector *params,
+                        const psVector *x)
+{
+    psF32 X  = x->data.F32[0] - params->data.F32[2];
+    psF32 Y  = x->data.F32[1] - params->data.F32[3];
+    psF32 px = params->data.F32[4]*X;
+    psF32 py = params->data.F32[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + params->data.F32[6]*X*Y;
+    psF32 r  = exp(-z);
+    psF32 q  = params->data.F32[1]*r;
+    psF32 f  = q + params->data.F32[0];
+
+    if (deriv != NULL) {
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2*px*params->data.F32[4] + params->data.F32[6]*Y);
+        deriv->data.F32[3] = q*(2*py*params->data.F32[5] + params->data.F32[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+    }
+    return(f);
+}
+
+psF64 pmModelFlux_TAUSS(const psVector *params)
+{
+    psF64 A1   = PS_SQR(params->data.F32[4]);
+    psF64 A2   = PS_SQR(params->data.F32[5]);
+    psF64 A3   = PS_SQR(params->data.F32[6]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    psF64 Flux = params->data.F32[1] * Area;
+
+    return(Flux);
+}
+
+// return the radius which yields the requested flux
+psF64 pmModelRadius_TAUSS  (const psVector *params, psF64 flux)
+{
+
+    if (flux <= 0)
+        return (1.0);
+    if (params->data.F32[1] <= 0)
+        return (1.0);
+    if (flux >= params->data.F32[1])
+        return (1.0);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    return (radius);
+}
+
+// define the parameter limits
+bool pmModelLimits_TAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max)
+{
+
+    *beta_lim   = psVectorAlloc (7, PS_TYPE_F32);
+    *params_min = psVectorAlloc (7, PS_TYPE_F32);
+    *params_max = psVectorAlloc (7, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +5.0;
+
+    return (TRUE);
+}
+
+// make an initial guess for parameters
+bool pmModelGuess_TAUSS (pmModel *model, pmSource *source)
+{
+
+    pmMoments *moments = source->moments;
+    psF32     *params  = model->params->data.F32;
+
+    params[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = moments->x;
+    params[3] = moments->y;
+    params[4] = 1.2 / moments->Sx;
+    params[5] = 1.2 / moments->Sy;
+    params[6] = 0.0;
+    return(true);
+}
+
+// construct the PSF model from the FLT model and the psf
+bool pmModelFromPSF_TAUSS (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf)
+{
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 7; i++) {
+        psPolynomial2D *poly = psf->params->data[i-4];
+        // XXX: be wary of a bug here.  EAM had his own version of psPolynomial2DEval().
+        // I think the reason was that hewas using nX=xOrder.  Not sure.  I changed this
+        // without verifying.
+        // out[i] = Polynomial2DEval_EAM (poly, out[2], out[3]);
+        out[i] = psPolynomial2DEval(poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+// check the status of the fitted model
+bool pmModelFitStatus_TAUSS (pmModel *model)
+{
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[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;
+}
Index: /tags/RHL_v1_1/psphot/src/models/pmModel_TGAUSS.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/models/pmModel_TGAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/models/pmModel_TGAUSS.c	(revision 9533)
@@ -0,0 +1,200 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z^M + z^N)
+
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = 
+    params->data.F32[8] = 
+*****************************************************************************/
+
+/* XXX EAM : we need a way to have user-set values for fix parameters */
+# define TG_S  2.60
+# define dTG_S 1.60
+# define TRF 0.50
+/* dTG_S is TG_S - 1.0 */
+
+psF32 pmModelFunc_TGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[PM_PAR_XPOS];
+    psF32 Y  = x->data.F32[1] - PAR[PM_PAR_YPOS];
+    psF32 px = PAR[PM_PAR_SXX]*X;
+    psF32 py = PAR[PM_PAR_SYY]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[PM_PAR_SXY]*X*Y;
+    psF32 er = exp(TRF*z);
+
+    psF32 r  = 1.0 / (er + PAR[PM_PAR_7]*z + pow(z, TG_S));    // (1/R)
+    psF32 f  = PAR[PM_PAR_I0]*r + PAR[PM_PAR_SKY];
+
+    if (deriv != NULL) {
+	psF32 t = PAR[PM_PAR_I0]*r*r;	// df/dR
+	psF32 q = t*(TRF*er + PAR[PM_PAR_7] + TG_S*pow(z, dTG_S));  // (df/dR)(dR/dz)
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[PM_PAR_SXX] + PAR[PM_PAR_SXY]*Y);
+	deriv->data.F32[3] = q*(2.0*py*PAR[PM_PAR_SYY] + PAR[PM_PAR_SXY]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -t*z;
+    }
+    return(f);
+}
+
+bool pmModelLimits_TGAUSS (psVector **beta_lim, psVector **params_min, psVector **params_max) {
+
+    *beta_lim   = psVectorAlloc (8, PS_TYPE_F32);
+    *params_min = psVectorAlloc (8, PS_TYPE_F32);
+    *params_max = psVectorAlloc (8, PS_TYPE_F32);
+
+    beta_lim[0][0].data.F32[0] = 1000;
+    beta_lim[0][0].data.F32[1] = 10000;
+    beta_lim[0][0].data.F32[2] = 5;
+    beta_lim[0][0].data.F32[3] = 5;
+    beta_lim[0][0].data.F32[4] = 0.5;
+    beta_lim[0][0].data.F32[5] = 0.5;
+    beta_lim[0][0].data.F32[6] = 0.5;
+    beta_lim[0][0].data.F32[7] = 0.5;
+
+    params_min[0][0].data.F32[0] = -1000;
+    params_min[0][0].data.F32[1] = 0;
+    params_min[0][0].data.F32[2] = -100;
+    params_min[0][0].data.F32[3] = -100;
+    params_min[0][0].data.F32[4] = 0.01;
+    params_min[0][0].data.F32[5] = 0.01;
+    params_min[0][0].data.F32[6] = -5.0;
+    params_min[0][0].data.F32[7] = 0.1;
+
+    params_max[0][0].data.F32[0] = 1e5;
+    params_max[0][0].data.F32[1] = 1e6;
+    params_max[0][0].data.F32[2] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[3] = 1e4;  // this should be set by image dimensions!
+    params_max[0][0].data.F32[4] = 2.0;
+    params_max[0][0].data.F32[5] = 2.0;
+    params_max[0][0].data.F32[6] = +5.0;
+    params_max[0][0].data.F32[7] = 10.0;
+
+    return (TRUE);
+}
+
+bool pmModelGuess_TGAUSS (pmModel *model, pmSource *source) {
+
+    pmMoments *moments = source->moments;
+    pmPeak    *peak    = source->peak;
+    psF32     *params  = model->params->data.F32;
+
+    params[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->y;
+    params[4] = 1.2 / moments->Sx;
+    params[5] = 1.2 / moments->Sy;
+    params[6] = 0.0;
+    params[7] = 1.0;
+    return(true);
+}
+
+psF64 pmModelFlux_TGAUSS(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[PM_PAR_SXX]);
+    psF64 A2   = PS_SQR(PAR[PM_PAR_SYY]);
+    psF64 A3   = PS_SQR(PAR[PM_PAR_SXY]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    for (z = 0.005; z < 50; z += 0.01) {
+        psF32 er = exp(TRF*z);
+	f = 1.0 / (er + PAR[PM_PAR_7]*z + pow(z, TG_S));
+	norm += f;
+    }
+    norm *= 0.01;
+    
+    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 pmModelRadius_TGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 z, f;
+    psF32 *PAR = params->data.F32;
+
+    if (flux <= 0) return (1.0);
+    if (PAR[PM_PAR_I0] <= 0) return (1.0);
+    if (flux >= PAR[PM_PAR_I0]) return (1.0);
+
+    // if Sx == Sy, sigma = Sx == Sy
+    psF64 sigma = hypot (1.0 / PAR[PM_PAR_SXX], 1.0 / PAR[PM_PAR_SYY]) / sqrt(2.0);
+    psF64 dz = 1.0 / (2.0 * sigma*sigma);
+    psF64 limit = flux / PAR[PM_PAR_I0];
+
+    // we can do this much better with intelligent choices here
+    for (z = 0.0; z < 20.0; z += dz) {
+        psF32 er = exp(TRF*z);
+	f = 1.0 / (er + PAR[PM_PAR_7]*z + pow(z, TG_S));
+	if (f < limit) break;
+    }
+    psF64 radius = sigma * sqrt (2.0 * z);
+    if (isnan(radius)) {
+      fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool pmModelFromPSF_TGAUSS (pmModel *modelPSF, pmModel *modelFLT, pmPSF *psf) {
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+    
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 8; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = psPolynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
+
+bool pmModelFitStatus_TGAUSS (pmModel *model) {
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[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;
+}
Index: /tags/RHL_v1_1/psphot/src/models/pmModel_WAUSS.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/models/pmModel_WAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/models/pmModel_WAUSS.c	(revision 9533)
@@ -0,0 +1,99 @@
+
+/******************************************************************************
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = sqrt(2.0) / SigmaX;
+    params->data.F32[5] = sqrt(2.0) / SigmaY;
+    params->data.F32[6] = Sxy;
+*****************************************************************************/
+
+psF64 psModelFunc_WAUSS(psVector *deriv,
+			const psVector *params,
+			const psVector *x)
+{
+    psF32 X = x->data.F32[0] - params->data.F32[2];
+    psF32 Y = x->data.F32[1] - params->data.F32[2];
+    psF32 px = params->data.F32[4]*X;
+    psF32 py = params->data.F32[5]*Y;
+    psF32 z = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + params->data.F32[6]*X*Y;
+    psF32 t = 0.5*z*z*(1.0 + params->data.F32[8]*z/3.0);
+    psF32 r = 1.0 / (1.0 + z + params->data.F32[7]*t); /* exp (-Z) */
+    psF32 f = params->data.F32[1]*r + params->data.F32[0];
+
+    if (deriv != NULL) {
+        // note difference from gaussian: q = params->data.F32[1]*r
+        psF32 q = params->data.F32[1]*r*r*(1.0 + params->data.F32[7]*z*(1.0 + params->data.F32[8]*z/2.0));
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*params->data.F32[4] + params->data.F32[6]*Y);
+        deriv->data.F32[3] = q*(2.0*py*params->data.F32[5] + params->data.F32[6]*X);
+        deriv->data.F32[4] = -2.0*q*px*X;
+        deriv->data.F32[5] = -2.0*q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -100.0*params->data.F32[1]*r*r*t;
+        deriv->data.F32[8] = -100.0*params->data.F32[1]*r*r*params->data.F32[7]*(z*z*z)/6.0;
+        // The values of 100 dampen the swing of params->data.F32[7,8] */
+    }
+    return(f);
+}
+
+// this is probably wrong since it uses the gauss integral 2 pi sigma^2
+psF64 psModelFlux_WAUSS(const psVector *params)
+{
+    psF64 A1   = 1 / PS_SQR(params->data.F32[4]);
+    psF64 A2   = 1 / PS_SQR(params->data.F32[5]);
+    psF64 A3   = params->data.F32[6];
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - PS_SQR(A3));
+    // Area is equivalent to 2 pi sigma^2
+
+    psF64 Flux = params->data.F32[1] * Area;
+
+    return(Flux);
+}
+
+// return the radius which yields the requested flux
+psF64 psModelRadius_WAUSS  (const psVector *params, psF64 flux)
+{
+    if (flux <= 0) return (1.0);
+    if (params->data.F32[1] <= 0) return (1.0);
+
+    psF64 sigma  = sqrt(2.0) * hypot (1.0 / params->data.F32[4], 1.0 / params->data.F32[5]);
+    psF64 radius = sigma * sqrt (2.0 * log(params->data.F32[1] / flux));
+    return (radius);
+}
+
+bool psModelGuess_WAUSS (psModel *model, pmSource *source) {
+
+    psVector *params = model->params;
+
+    params->data.F32[0] = source->moments->Sky;
+    params->data.F32[1] = source->peak->counts - source->moments->Sky;
+    params->data.F32[2] = source->moments->x;
+    params->data.F32[3] = source->moments->y;
+    params->data.F32[4] = sqrt(2.0) / source->moments->Sx;
+    params->data.F32[5] = sqrt(2.0) / source->moments->Sy;
+    params->data.F32[6] = source->moments->Sxy;
+    // XXX: What are these?
+    // params->data.F32[7] = B2;
+    // params->data.F32[8] = B3;
+    return(true);
+}
+
+bool psModelFromPSF_WAUSS (psModel *modelPSF, psModel *modelFLT, pmPSF *psf) {
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+    
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 9; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = Polynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
Index: /tags/RHL_v1_1/psphot/src/models/pmModel_ZGAUSS.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/models/pmModel_ZGAUSS.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/models/pmModel_ZGAUSS.c	(revision 9533)
@@ -0,0 +1,158 @@
+
+/******************************************************************************
+    one component, two slopes:
+    1 / (1 + z^Npow + PAR8*z^4)
+
+    params->data.F32[0] = So;
+    params->data.F32[1] = Zo;
+    params->data.F32[2] = Xo;
+    params->data.F32[3] = Yo;
+    params->data.F32[4] = 1 / SigmaX;
+    params->data.F32[5] = 1 / SigmaY;
+    params->data.F32[6] = Sxy;
+    params->data.F32[7] = Npow
+*****************************************************************************/
+
+# define SQ(A)((A)*(A))
+# define PAR8 0.1
+
+psF64 psModelFunc_ZGAUSS(psVector *deriv,
+			 const psVector *params,
+			 const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[PM_PAR_XPOS];
+    psF32 Y  = x->data.F32[1] - PAR[PM_PAR_YPOS];
+    psF32 px = PAR[PM_PAR_SXX]*X;
+    psF32 py = PAR[PM_PAR_SYY]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[PM_PAR_SXY]*X*Y;
+
+    psF32 pr = PAR8*z;
+    psF32 p  = pow(z, PAR[PM_PAR_7] - 1.0);
+    psF32 r  = 1.0 / (1 + z*p + SQ(SQ(pr)));
+    psF32 f  = PAR[PM_PAR_I0]*r + PAR[PM_PAR_SKY];
+
+    if (deriv != NULL) {
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = PAR[PM_PAR_I0]*r*r;
+	psF32 q = t*(PAR[PM_PAR_7]*p + 4*PAR8*pr*pr*pr);
+
+        deriv->data.F32[0] = +1.0;
+        deriv->data.F32[1] = +r;
+        deriv->data.F32[2] = q*(2.0*px*PAR[PM_PAR_SXX] + PAR[PM_PAR_SXY]*Y);
+        deriv->data.F32[3] = q*(2.0*py*PAR[PM_PAR_SYY] + PAR[PM_PAR_SXY]*X);
+        deriv->data.F32[4] = -q*px*X;
+        deriv->data.F32[5] = -q*py*Y;
+        deriv->data.F32[6] = -q*X*Y;
+        deriv->data.F32[7] = -t*log(z)*z*p;
+    }
+    return(f);
+}
+
+psF64 psModelFlux_ZGAUSS(const psVector *params)
+{
+    float f, norm, z;
+
+    psF32 *PAR = params->data.F32;
+
+    psF64 A1   = PS_SQR(PAR[PM_PAR_SXX]);
+    psF64 A2   = PS_SQR(PAR[PM_PAR_SYY]);
+    psF64 A3   = PS_SQR(PAR[PM_PAR_SXY]);
+    psF64 Area = 2.0 * M_PI / sqrt(A1*A2 - A3);
+    // Area is equivalent to 2 pi sigma^2
+
+    // the area needs to be multiplied by the integral of f(z)
+    norm = 0.0;
+    psF32 pr = PAR8*z;
+    for (z = 0.005; z < 50; z += 0.01) {
+	f = 1.0 / (1 + pow(z, PAR[PM_PAR_7]) + SQ(SQ(pr)));
+	norm += f;
+    }
+    norm *= 0.01;
+    
+    psF64 Flux = PAR[PM_PAR_I0] * Area * norm;
+
+    return(Flux);
+}
+
+// XXX need to define the radius along the major axis
+// define this function so it never returns Inf or NaN
+// return the radius which yields the requested flux
+psF64 psModelRadius_ZGAUSS  (const psVector *params, psF64 flux)
+{
+    psF64 r, z, pr, f;
+    psF32 *PAR = params->data.F32;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+
+    if (flux <= 0) return (1.0);
+    if (PAR[PM_PAR_I0] <= 0) return (1.0);
+    if (flux >= PAR[PM_PAR_I0]) return (1.0);
+
+    // convert Sx,Sy,Sxy to major/minor axes
+    shape.sx = 1.0 / PAR[PM_PAR_SXX];
+    shape.sy = 1.0 / PAR[PM_PAR_SYY];
+    shape.sxy = PAR[PM_PAR_SXY];
+
+    axes = EllipseShapeToAxes (shape);
+    psF64 dr = 1.0 / axes.major;
+    psF64 limit = flux / PAR[PM_PAR_I0];
+
+    // XXX : we can do this faster with an intelligent starting choice
+    for (r = 0.0; r < 20.0; r += dr) {
+	z = SQ(r);
+	pr = PAR8*z;
+	f = 1.0 / (1 + pow(z, PAR[PM_PAR_7]) + SQ(SQ(pr)));
+	if (f < limit) break;
+    }
+    psF64 radius = 2.0 * axes.major * sqrt (z);
+    if (isnan(radius)) {
+	fprintf (stderr, "error in code\n");
+    }
+    return (radius);
+}
+
+bool psModelGuess_ZGAUSS (psModel *model, psSource *source) {
+
+    psVector *params = model->params;
+
+    EllipseAxes axes;
+    EllipseShape shape;
+    EllipseMoments moments;
+    
+    moments.x2 = PS_SQR(source->moments->Sx);
+    moments.y2 = PS_SQR(source->moments->Sy);
+    moments.xy = source->moments->Sxy;
+
+    axes = EllipseMomentsToAxes(moments);
+    shape = EllipseAxesToShape(axes);
+
+    params->data.F32[0] = source->moments->Sky;
+    params->data.F32[1] = source->peak->counts - source->moments->Sky;
+    params->data.F32[2] = source->moments->x;
+    params->data.F32[3] = source->moments->y;
+    params->data.F32[4] = 1.0 / shape.sx;
+    params->data.F32[5] = 1.0 / shape.sy;
+    params->data.F32[6] = shape.sxy;
+    params->data.F32[7] = 1.9;
+    return(true);
+}
+
+bool psModelFromPSF_ZGAUSS (psModel *modelPSF, psModel *modelFLT, pmPSF *psf) {
+
+    psF32 *out = modelPSF->params->data.F32;
+    psF32 *in  = modelFLT->params->data.F32;
+    
+    out[0] = in[0];
+    out[1] = in[1];
+    out[2] = in[2];
+    out[3] = in[3];
+
+    for (int i = 4; i < 8; i++) {
+	psPolynomial2D *poly = psf->params->data[i-4];
+	out[i] = Polynomial2DEval (poly, out[2], out[3]);
+    }
+    return(true);
+}
Index: /tags/RHL_v1_1/psphot/src/psphot.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphot.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphot.c	(revision 9533)
@@ -0,0 +1,40 @@
+# include "psphot.h"
+
+static void usage (void) {
+    fprintf (stderr, "USAGE: ppImage [-file INPUT.fits] [-list INPUT.txt] OUTPUT\n");
+    exit (2);
+}
+
+int main (int argc, char **argv) {
+
+    psTimerStart ("complete");
+
+    psphotErrorRegister();              // register our error codes/messages
+
+    // basic pslib options
+    psLogSetFormat ("M");
+
+    // load implementation-specific models
+    psphotModelGroupInit ();
+
+    // load command-line arguments, options, and system config data
+    pmConfig *config = psphotArguments (argc, argv);
+    if (config == NULL) usage ();
+
+    // load input data (config and images (signal, noise, mask)
+    if (!psphotParseCamera (config)) {
+        psErrorStackPrint(stderr, " ");
+        exit(1);
+    }
+
+    // call psphot for each readout
+    if (!psphotImageLoop (config)) {
+        psErrorStackPrint(stderr, " ");
+        exit(1);
+    }
+
+    psLogMsg ("psphot", 3, "complete psphot run: %f sec\n", psTimerMark ("complete"));
+
+    psphotCleanup (config);
+    exit (EXIT_SUCCESS);
+}
Index: /tags/RHL_v1_1/psphot/src/psphot.h
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphot.h	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphot.h	(revision 9533)
@@ -0,0 +1,83 @@
+# ifdef HAVE_CONFIG_H
+# include <config.h>
+# endif
+
+# include <stdio.h>
+# include <strings.h>  // for strcasecmp
+# include <unistd.h>   // for unlink
+# include <pslib.h>
+# include <psmodules.h>
+
+# include "psphotErrorCodes.h"
+
+# define PSPHOT_RECIPE "PSPHOT"
+
+// top-level psphot functions
+const char *psphotCVSName(void);
+
+pmConfig       *psphotArguments (int argc, char **argv);
+bool            psphotParseCamera (pmConfig *config);
+bool            psphotImageLoop (pmConfig *config);
+
+bool            psphotModelTest (pmReadout *readout, psMetadata *recipe);
+bool            psphotReadout (pmConfig *config, pmFPAview *view);
+void            psphotCleanup (pmConfig *config);
+
+psArray        *psphotFakeSources ();
+
+// psphotReadout functions
+bool            psphotImageMedian (pmConfig *config, pmFPAview *view);
+psArray        *psphotFindPeaks (pmReadout *readout, psMetadata *recipe);
+psArray        *psphotSourceStats (pmReadout *readout, psMetadata *recipe, psArray *allpeaks);
+bool            psphotRoughClass (psArray *sources, psMetadata *recipe);
+bool            psphotBasicDeblend (psArray *sources, psMetadata *recipe);
+pmPSF          *psphotChoosePSF (pmReadout *readout, psArray *sources, psMetadata *recipe);
+bool            psphotPSFstats (pmReadout *readout, psMetadata *recipe, pmPSF *psf);
+bool            psphotMomentsStats (pmReadout *readout, psMetadata *recipe, psArray *sources);
+bool            psphotEnsemblePSF (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf, bool final);
+bool            psphotBlendFit (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf);
+bool            psphotReplaceUnfit (psArray *sources);
+bool            psphotApResid (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf);
+bool            psphotMagnitudes (psArray *sources, psMetadata *recipe, pmPSF *psf);
+bool            psphotSkyReplace (pmConfig *config, pmFPAview *view);
+bool            psphotReadoutCleanup (pmConfig *config, pmReadout *readout, psMetadata *recipe, pmPSF *psf, psArray *sources);
+
+// basic support functions
+void            psphotModelGroupInit (void);
+int             psphotSortBySN (const void **a, const void **b);
+int             psphotSortByY (const void **a, const void **b);
+bool            psphotGrowthCurve (pmReadout *readout, pmPSF *psf);
+void            psphotTestArguments (int *argc, char **argv);
+bool            psphotMaskReadout (pmReadout *readout, psMetadata *recipe);
+void            psphotSourceFreePixels (psArray *sources);
+
+// functions to set the correct source pixels
+bool            psphotInitRadiusPSF (psMetadata *recipe, pmModelType type);
+bool            psphotCheckRadiusPSF (pmReadout *readout, pmSource *source, pmModel *model);
+bool            psphotCheckRadiusPSFBlend (pmReadout *readout, pmSource *source, pmModel *model, float dR);
+bool            psphotInitRadiusEXT (psMetadata *recipe, pmModelType type);
+bool            psphotCheckRadiusEXT (pmReadout *readout, pmSource *source, pmModel *model);
+
+// output functions
+bool            psphotDumpMoments (psMetadata *recipe, psArray *sources);
+psMetadata     *psphotDefineHeader (psMetadata *recipe);
+bool            psphotWeightBias (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf);
+int             psphotSaveImage (psMetadata *header, psImage *image, char *filename);
+
+// 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);
+bool            psphotInitLimitsEXT (psMetadata *recipe);
+bool            psphotFitBlend (pmReadout *readout, pmSource *source, pmPSF *psf);
+bool            psphotFitBlob (pmReadout *readout, pmSource *source, psArray *sources, pmPSF *psf);
+bool            psphotFitPSF (pmReadout *readout, pmSource *source, pmPSF *psf);
+pmModel        *psphotFitEXT (pmReadout *readout, pmSource *source);
+psArray        *psphotFitDBL (pmReadout *readout, pmSource *source);
+
+// functions to support simultaneous multi-source fitting
+bool            psphotFitSet (pmSource *oneSrc, pmModel *oneModel, char *fitset, pmSourceFitMode mode);
+
Index: /tags/RHL_v1_1/psphot/src/psphotApResid.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotApResid.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotApResid.c	(revision 9533)
@@ -0,0 +1,345 @@
+# include "psphot.h"
+static char DEFAULT_OPTION[] = "SKYBIAS";
+
+// measure the aperture residual statistics
+bool psphotApResid (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) { 
+
+    int Nfail = 0;
+    int Nskip = 0;
+    int Npsf;
+    bool status;
+    pmModel *model;
+    pmSource *source;
+
+    psTimerStart ("psphot");
+
+    // S/N limit to perform full non-linear fits
+    float MAX_AP_OFFSET = psMetadataLookupF32 (&status, recipe, "MAX_AP_OFFSET");
+
+    // set limits on the aperture magnitudes 
+    pmSourceMagnitudesInit (recipe);
+
+    // measure the aperture loss as a function of radius for PSF
+    float REF_RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_REF_RADIUS");
+    psf->growth = pmGrowthCurveAlloc (3.0, 100.0, REF_RADIUS);
+    psphotGrowthCurve (readout, psf);
+    
+    psVector *mask    = psVectorAlloc (300, PS_TYPE_U8);
+    psVector *xPos    = psVectorAlloc (300, PS_TYPE_F64);
+    psVector *yPos    = psVectorAlloc (300, PS_TYPE_F64);
+    psVector *flux    = psVectorAlloc (300, PS_TYPE_F64);
+    psVector *r2rflux = psVectorAlloc (300, PS_TYPE_F64);
+    psVector *apResid = psVectorAlloc (300, PS_TYPE_F64);
+    psVector *dMag    = psVectorAlloc (300, PS_TYPE_F64);
+    mask->n = xPos->n = yPos->n = flux->n = r2rflux->n = apResid->n = dMag->n = 0;
+    Npsf = 0;
+
+    // select all good PM_SOURCE_TYPE_STAR entries
+    for (int i = 0; i < sources->n; i++) {
+	source = sources->data[i];
+	model = source->modelPSF;
+
+	if (source->type != PM_SOURCE_TYPE_STAR) continue; 
+	if (source->mode &  PM_SOURCE_MODE_SATSTAR) continue;
+	if (source->mode &  PM_SOURCE_MODE_BLEND) continue;
+	if (source->mode &  PM_SOURCE_MODE_FAIL) continue;
+	if (source->mode &  PM_SOURCE_MODE_POOR) continue;
+
+	// get growth-corrected, apTrend-uncorrected magnitudes in scaled apertures
+	// will fail if below S/N threshold or model is missing
+	if (!pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH)) {
+	    Nskip ++;
+	    continue;
+	}
+
+	apResid->data.F64[Npsf] = source->apMag - source->psfMag;
+
+	xPos->data.F64[Npsf]    = model->params->data.F32[PM_PAR_XPOS];
+	yPos->data.F64[Npsf]    = model->params->data.F32[PM_PAR_YPOS];
+
+	flux->data.F64[Npsf]    = pow(10.0, -0.4*source->psfMag);
+	r2rflux->data.F64[Npsf] = PS_SQR(model->radiusTMP) / flux->data.F64[Npsf];
+
+	mask->data.U8[Npsf] = 0;
+
+	// XXX sanity clip?
+	// XXX need to see if all data were tossed?
+	// XXX need to subtract median?
+	// XXX need to put this in the config data...
+	// if (fabs(apResid->data.F64[Npsf]) > 0.2) continue;
+	if ((MAX_AP_OFFSET > 0) && (fabs(apResid->data.F64[Npsf]) > MAX_AP_OFFSET)) {
+	    Nfail ++;
+	    continue;
+	}
+
+	dMag->data.F64[Npsf] = model->dparams->data.F32[PM_PAR_I0] / model->params->data.F32[PM_PAR_I0];
+
+	psVectorExtend (mask, 	 100, 1);
+	psVectorExtend (xPos, 	 100, 1);
+	psVectorExtend (yPos, 	 100, 1);
+	psVectorExtend (flux,    100, 1);
+	psVectorExtend (r2rflux, 100, 1);
+	psVectorExtend (dMag,    100, 1);
+	psVectorExtend (apResid, 100, 1);
+	Npsf ++;
+    }
+    psLogMsg ("psphot.apresid", 4, "measure aperture residuals : %f sec for %d objects (%d skipped, %d failed, %ld invalid)\n", 
+	      psTimerMark ("psphot"), Npsf, Nskip, Nfail, sources->n - Npsf - Nskip - Nfail);
+
+    // XXX choose a better value here?
+    if (Npsf < 5) {
+	psError(PSPHOT_ERR_APERTURE, true, "no valid aperture residual sources, giving up");
+	return false;
+    }
+
+    // APTREND options are defined by the if ... else if ... else if ... else block below
+    char *ApTrendOption = psMetadataLookupPtr (&status, recipe, "APTREND");
+    if (!status) ApTrendOption = DEFAULT_OPTION;
+
+    // 3hi/1lo sigma clipping on the rflux vs metric fit
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    stats->min = 2.0;
+    stats->max = 3.0;
+
+    // no correction
+    if (!strcasecmp (ApTrendOption, "NONE")) { 
+	// remove ApTrend fit from pmPSFtry
+	psf->ApTrend->coeff[0][0][0][0] = 0;
+    } else if (!strcasecmp (ApTrendOption, "CONSTANT")) { // constant only
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting nothing");
+	    return false;
+	}
+
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "Fitting aperture correction");
+	    return false;
+	}
+    } else if (!strcasecmp (ApTrendOption, "SKYBIAS")) { // constant and skybias only
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting nothing");
+	    return false;
+	}
+
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_SKYBIAS);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting sky bias");
+	    return false;
+	}
+    } else if (!strcasecmp (ApTrendOption, "SKYSAT")) {
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting nothing");
+	    return false;
+	}
+
+	// apply the fit
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_SKYBIAS);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting sky bias");
+	    return false;
+	}
+
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_SKYSAT);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting skysat");
+	    return false;
+	}
+    } else if (!strcasecmp (ApTrendOption, "XY_LIN")) {	// constant and linear X,Y only
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting nothing");
+	    return false;
+	}
+
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_XY_LIN);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "fitting, XY_LIN");
+	    return false;
+	}
+    } else if (!strcasecmp (ApTrendOption, "XY_QUAD")) { // constant and quadratic X,Y only
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting nothing");
+	    return false;
+	}
+
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_XY_QUAD);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "Fitting XY_QUAD");
+	    return false;
+	}
+    } else if (!strcasecmp (ApTrendOption, "SKY_XY_LIN")) { // constant and sky, linear X,Y only
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting nothing");
+	    return false;
+	}
+
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_SKY_XY_LIN);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "Fitting sky xy_lin");
+	    return false;
+	}
+    } else if (!strcasecmp (ApTrendOption, "SKYSAT_XY_LIN")) { // constant and sky, linear X,Y only
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting nothing");
+	    return false;
+	}
+
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_SKYBIAS);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "clipping, fitting sky bias");
+	    return false;
+	}
+
+	// apply the fit
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_SKYSAT_XY_LIN);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "Fitting skyset xy_lin");
+	    return false;
+	}
+    } else if (!strcasecmp (ApTrendOption, "ALL")) {
+	// first clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_CONSTANT);
+	psf->ApTrend  = psVectorClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "Failed to measure apTrend");
+	    return false;
+	}
+
+	// fit just SkyBias and clip out objects which are too far from the median 
+	stats->clipIter = 2;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_SKYBIAS);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "fitting skyBias");
+	    return false;
+	}
+
+	// finally, fit x, y, SkyBias and clip out objects which are too far from the median 
+	stats->clipIter = 3;
+	pmPSF_MaskApTrend (psf->ApTrend, PM_PSF_ALL);
+	psf->ApTrend  = psVectorChiClipFitPolynomial4D (psf->ApTrend, stats, mask, PSFTRY_MASK_ALL, apResid, dMag, xPos, yPos, r2rflux, flux);
+	if (psf->ApTrend == NULL) {
+	    psError(PSPHOT_ERR_PHOTOM, false, "fitting all");
+	    return false;
+	}
+    } else {
+	psError(PSPHOT_ERR_PHOTOM, true, "Unknown APTREND value: %s", ApTrendOption);
+	return false;
+    }
+
+    // construct the fitted values and the residuals
+    psVector *fit   = psPolynomial4DEvalVector (psf->ApTrend, xPos, yPos, r2rflux, flux);
+    psVector *resid = (psVector *) psBinaryOp (NULL, (void *) apResid, "-", (void *) fit);
+
+    // measure scatter for sources with dMag < 0.01 (S/N = 100)
+    int Nkeep = 0;
+    psStats *residStats = psStatsAlloc (PS_STAT_SAMPLE_STDEV);
+    for (int i = 0; i < dMag->n; i++) {
+	if (dMag->data.F64[i] > 0.01) {
+	    mask->data.U8[i] |= 0x02;
+	} 
+	if (! mask->data.U8[i]) Nkeep ++;
+    }
+    residStats  = psVectorStats (residStats, resid, NULL, mask, 0x03);
+
+    // apply ApTrend results
+    psf->skyBias  = psf->ApTrend->coeff[0][0][1][0];
+    psf->skySat   = psf->ApTrend->coeff[0][0][0][1];
+    psf->ApResid  = psf->ApTrend->coeff[0][0][0][0];
+    psf->dApResid = residStats->sampleStdev;
+    psf->ApTrend->coeff[0][0][1][0] = 0;
+    psf->ApTrend->coeff[0][0][0][1] = 0;
+    psf->nApResid = Npsf;
+
+    // save results for later output
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKYBIAS",  PS_DATA_F32 | PS_META_REPLACE, "aperture sky bias",   psf->skyBias);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKYSAT",   PS_DATA_F32 | PS_META_REPLACE, "aperture sky bias",   psf->skySat);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "APMIFIT",  PS_DATA_F32 | PS_META_REPLACE, "aperture residual",   psf->ApResid);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "DAPMIFIT", PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->dApResid);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "APLOSS",   PS_DATA_F32 | PS_META_REPLACE, "ap residual scatter", psf->growth->apLoss);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "NAPMIFIT", PS_DATA_S32 | PS_META_REPLACE, "ap residual scatter", psf->nApResid);
+
+    psLogMsg ("psphot.apresid", 3, "measure full-frame aperture residual: %f sec\n", psTimerMark ("psphot"));
+    psLogMsg ("psphot.apresid", 4, "aperture residual: %f +/- %f : %f bias, %f skysat (%d of %d used)\n", 
+	      psf->ApResid, psf->dApResid, psf->skyBias, psf->skySat, Nkeep, Npsf);
+    psLogMsg ("psphot.apresid", 4, "apresid trends: %f %f %f %f %f\n", 
+	      1e3*psf->ApTrend->coeff[1][0][0][0],
+	      1e6*psf->ApTrend->coeff[2][0][0][0],
+	      1e6*psf->ApTrend->coeff[1][1][0][0],
+	      1e3*psf->ApTrend->coeff[0][1][0][0],
+	      1e6*psf->ApTrend->coeff[0][2][0][0]);
+
+    psFree (mask);
+    psFree (xPos);
+    psFree (yPos);
+    psFree (flux);
+    psFree (r2rflux);
+    psFree (apResid);
+    psFree (dMag);
+
+    psFree (fit);
+    psFree (resid);
+    psFree (stats);
+    psFree (residStats);
+    return true;
+}
+
+/*
+  (aprMag' - fitMag) = rflux*skyBias + ApTrend(x,y)
+  (aprMag - rflux*skyBias) - fitMag = ApTrend(x,y)
+  (aprMag - rflux*skyBias) = fitMag + ApTrend(x,y)
+*/
+
Index: /tags/RHL_v1_1/psphot/src/psphotArguments.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotArguments.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotArguments.c	(revision 9533)
@@ -0,0 +1,136 @@
+# include "psphot.h"
+# include <glob.h>
+
+pmConfig *psphotArguments(int argc, char **argv) {
+
+    int N;
+    bool status;
+
+    if (argc == 1) {
+        psError(PSPHOT_ERR_ARGUMENTS, true, "Too few arguments: %d", argc);
+        return NULL;
+    }
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv);
+    if (config == NULL) {
+        psError(PSPHOT_ERR_CONFIG, false, "Can't read site configuration");
+        return NULL;
+    }
+
+    // save the recipe options separately until we have loaded the recipe (psphotParseDetrend)
+    psMetadata *options = psMetadataAlloc ();
+    psMetadataAddPtr (config->arguments, PS_LIST_TAIL, "PSPHOT.OPTIONS",  PS_DATA_METADATA, "", options);
+
+    // the following options override the PSPHOT recipe options
+
+    // run the test model (requires X,Y coordinate)
+    if ((N = psArgumentGet (argc, argv, "-modeltest"))) {
+        psMetadataAddBool (options, PS_LIST_TAIL, "TEST_FIT",   0, "", true);
+        psMetadataAddF32  (options, PS_LIST_TAIL, "TEST_FIT_X", 0, "", atof(argv[N+1]));
+        psMetadataAddF32  (options, PS_LIST_TAIL, "TEST_FIT_Y", 0, "", atof(argv[N+2]));
+
+        psArgumentRemove (N, &argc, argv);
+        psArgumentRemove (N, &argc, argv);
+        psArgumentRemove (N, &argc, argv);
+
+        // specify the modeltest model
+        if ((N = psArgumentGet (argc, argv, "-model"))) {
+            psArgumentRemove (N, &argc, argv);
+            psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_MODEL", 0, "", argv[N]);
+            psArgumentRemove (N, &argc, argv);
+        }
+
+        // specify the test fit mode
+        if ((N = psArgumentGet (argc, argv, "-fitmode"))) {
+            psArgumentRemove (N, &argc, argv);
+            psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_MODE", 0, "", argv[N]);
+            psArgumentRemove (N, &argc, argv);
+        }
+        if ((N = psArgumentGet (argc, argv, "-fitset"))) {
+            psArgumentRemove (N, &argc, argv);
+            psMetadataAddStr (options, PS_LIST_TAIL, "TEST_FIT_SET", 0, "", argv[N]);
+            psArgumentRemove (N, &argc, argv);
+        }
+    }
+
+    // photcode : used in output to supplement header data (argument or recipe?)
+    if ((N = psArgumentGet (argc, argv, "-photcode"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // break : used from recipe throughout psphotReadout
+    if ((N = psArgumentGet (argc, argv, "-break"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "BREAK_POINT", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // fitmode : used from recipe throughout psphotReadout
+    if ((N = psArgumentGet (argc, argv, "-fitmode"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "FITMODE", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // analysis region : overrides recipe value, used in psphotReadout/psphotEnsemblePSF
+    if ((N = psArgumentGet (argc, argv, "-region"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "ANALYSIS_REGION", 0, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // other arbitrary recipe values: -D key value (all added as string)
+    while ((N = psArgumentGet (argc, argv, "-D"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, argv[N], 0, "", argv[N+1]);
+        psArgumentRemove (N, &argc, argv);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // other arbitrary recipe values: -Df key value (all added as float)
+    while ((N = psArgumentGet (argc, argv, "-Df"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddF32 (options, PS_LIST_TAIL, argv[N], 0, "", atof(argv[N+1]));
+        psArgumentRemove (N, &argc, argv);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // other arbitrary recipe values: -Di key value (all added as int)
+    while ((N = psArgumentGet (argc, argv, "-Di"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddS32 (options, PS_LIST_TAIL, argv[N], 0, "", atoi(argv[N+1]));
+        psArgumentRemove (N, &argc, argv);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // drop the local view on the options (saved on config->arguments)
+    psFree (options);
+
+    // the input file is a required argument; if not found, we will exit
+    status = pmConfigFileSetsMD (config->arguments, config, "INPUT", "-file", "-list");
+    if (!status) {
+        psError(PSPHOT_ERR_ARGUMENTS, false, "pmConfigFileSetsMD failed to parse arguments");
+        return NULL;
+    }
+
+    // chip selection is used to limit chips to be processed
+    if ((N = psArgumentGet (argc, argv, "-chip"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (config->arguments, PS_LIST_TAIL, "CHIP_SELECTIONS", PS_DATA_STRING, "", psStringCopy(argv[N]));
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    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(__func__, 1, "Done with psphotArguments...\n");
+    return (config);
+}
Index: /tags/RHL_v1_1/psphot/src/psphotBasicDeblend.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotBasicDeblend.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotBasicDeblend.c	(revision 9533)
@@ -0,0 +1,130 @@
+# include "psphot.h"
+
+// 2006.02.07 : no leaks
+bool psphotBasicDeblend (psArray *sources, psMetadata *recipe) { 
+
+    int N;
+    bool status;
+    float threshold;
+    pmSource *source, *testSource;
+
+    int Nblend = 0;
+
+    psTimerStart ("psphot");
+
+    float FRACTION = psMetadataLookupF32 (&status, recipe, "DEBLEND_PEAK_FRACTION");
+    if (!status) FRACTION = 0.25;
+
+    float NSIGMA   = psMetadataLookupF32 (&status, recipe, "DEBLEND_SKY_NSIGMA");
+    if (!status) NSIGMA = 5.0;
+
+    // we need sources spatially-sorted to find overlaps
+    sources = psArraySort (sources, psphotSortByY);
+
+    // source analysis is done in peak order (brightest first)
+    // we use an index for this so the spatial sorting is kept
+    psVector *SN = psVectorAlloc (sources->n, PS_DATA_F32);
+    SN->n = SN->nalloc;
+    for (int i = 0; i < SN->n; i++) {
+	source = sources->data[i];
+	SN->data.F32[i] = source->peak->counts;
+    }
+    psVector *index = psVectorSortIndex (NULL, SN);
+    // this results in an index of increasing SN
+
+    // examine sources in decreasing SN order
+    for (int i = sources->n - 1; i >= 0; i--) {
+	N = index->data.U32[i];
+	source = sources->data[N];
+
+	if (source->mode & PM_SOURCE_MODE_BLEND) continue;
+
+	// temporary array for overlapping objects we find
+	psArray *overlap = psArrayAlloc (100);
+	// DROP overlap->n = 0;
+
+	// search backwards for overlapping sources
+	for (int j = N - 1; j >= 0; j--) {
+	    testSource = sources->data[j];
+	    if (testSource->peak->x <  source->pixels->col0) continue;
+	    if (testSource->peak->x >= source->pixels->col0 + source->pixels->numCols) continue;
+	    if (testSource->peak->y <  source->pixels->row0) break;
+	    if (testSource->peak->y >= source->pixels->row0 + source->pixels->numRows) {
+		fprintf (stderr, "warning: invalid condition\n");
+		continue;
+	    }
+	    psArrayAdd (overlap, 100, testSource);
+	}
+
+	// search forwards for overlapping sources
+	for (int j = N + 1; j < sources->n; j++) {
+	    testSource = sources->data[j];
+	    if (testSource->peak->x <  source->pixels->col0) continue;
+	    if (testSource->peak->x >= source->pixels->col0 + source->pixels->numCols) continue;
+	    if (testSource->peak->y <  source->pixels->row0) {
+		fprintf (stderr, "warning: invalid condition\n");
+		continue;
+	    }
+	    if (testSource->peak->y >= source->pixels->row0 + source->pixels->numRows) break;
+	    psArrayAdd (overlap, 100, testSource);
+	}
+
+	if (overlap->n == 0) {
+	    psFree (overlap);
+	    continue;
+	}
+
+	// this source has overlapping neighbors, check for actual blends
+	// generate source contour (1/4 peak counts)
+	// set the threshold based on user inputs
+	
+	// threshold is fraction of the source peak flux
+	// image is background subtracted; source->moments->Sky should always be 0.0
+	threshold = FRACTION * source->moments->Peak;
+	// threshold is no less than NSIGMA above the local median sigma?
+	threshold = PS_MAX (threshold, NSIGMA*sqrt(source->moments->dSky));
+
+	// generate a basic contour (set of x,y coordinates at-or-below flux level)
+	psArray *contour = pmSourceContour (source->pixels, source->peak->x, source->peak->y, threshold);
+	if (contour == NULL) {
+	    psFree (overlap);
+	    continue;
+	}
+
+	// the source contour consists of two vectors, xv and yv.  the contour is 
+	// a series of coordinate pairs, (xv[i],yv[i]) & (xv[i+1],yv[i+1]).  both
+	// coordinate pairs have the same yv[] value, with xv[i] corresponding to
+	// the left boundary and xv[i+1] corresponding to the right boundary
+
+	psVector *xv = contour->data[0];
+	psVector *yv = contour->data[1];
+	for (int k = 0; k < overlap->n; k++) {
+	    testSource = overlap->data[k];
+	    if (testSource->peak->counts > source->peak->counts) continue;
+	    for (int j = 0; j < xv->n; j+=2) {
+		if (fabs(yv->data.F32[j] - testSource->peak->y) > 0.5) continue;
+		if (xv->data.F32[j+0] > testSource->peak->x) break;
+		if (xv->data.F32[j+1] < testSource->peak->x) break;
+
+		testSource->mode |= PM_SOURCE_MODE_BLEND;
+
+		// add this to the list of source->blends
+		if (source->blends == NULL) {
+		    source->blends = psArrayAlloc (16);
+		    // DROP source->blends->n = 0;
+		}
+		psArrayAdd (source->blends, 16, testSource);
+
+		Nblend ++;
+		j = xv->n;
+	    }
+	}  
+	psFree (overlap);
+	psFree (contour);
+    }
+    psLogMsg ("psphot.deblend", 3, "identified %d blended objects (%f sec)\n", Nblend, psTimerMark ("psphot"));
+
+    psFree (SN);
+    psFree (index);
+    return true;
+}
Index: /tags/RHL_v1_1/psphot/src/psphotBlendFit.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotBlendFit.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotBlendFit.c	(revision 9533)
@@ -0,0 +1,88 @@
+# include "psphot.h"
+
+// XXX I don't like this name
+bool psphotBlendFit (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) { 
+
+    int Nfit = 0;
+    int Npsf = 0;
+    int Next = 0;
+    int Nfail = 0;
+    bool status;
+
+    psTimerStart ("psphot");
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, psphotSortBySN);
+    
+    // S/N limit to perform full non-linear fits
+    float FIT_SN_LIM = psMetadataLookupF32 (&status, recipe, "FULL_FIT_SN_LIM");
+
+    psphotInitLimitsPSF (recipe);
+    psphotInitLimitsEXT (recipe);
+    psphotInitRadiusPSF (recipe, psf->type);
+
+    // 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 ("psphot", "analysis region mis-defined");
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+
+	// skip non-astronomical objects (very likely defects)
+	if (source->mode &  PM_SOURCE_MODE_BLEND) continue;
+	if (source->type == PM_SOURCE_TYPE_DEFECT) continue; 
+	if (source->type == PM_SOURCE_TYPE_SATURATED) continue;
+
+	// skip DBL second sources (ie, added by psphotFitBlob)
+	if (source->mode &  PM_SOURCE_MODE_PAIR) continue;
+
+	// limit selection to some SN limit
+	if (source->moments == NULL) continue;
+	if (source->moments->SN < FIT_SN_LIM) continue;
+
+	if (source->moments->x < AnalysisRegion.x0) continue;
+	if (source->moments->y < AnalysisRegion.y0) continue;
+	if (source->moments->x > AnalysisRegion.x1) continue;
+	if (source->moments->y > AnalysisRegion.y1) continue;
+
+	// if model is NULL, we don't have a starting guess
+	if (source->modelPSF == NULL) continue;
+
+	// skip sources which are insignificant flux?
+	if (source->modelPSF->params->data.F32[1] < 0.1) {
+	    psTrace ("psphotBlendFit", 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
+	pmModelAdd (source->pixels, source->mask, source->modelPSF, false, false);
+	Nfit ++;
+
+	// try fitting PSFs, then try extended sources
+	if (psphotFitBlend (readout, source, psf)) { 
+	    psTrace ("psphotBlendFit", 5, "source at %7.1f, %7.1f is psf", source->moments->x, source->moments->y);
+	    Npsf ++;
+	    continue;
+	}
+	if (psphotFitBlob (readout, source, sources, psf)) {
+	    psTrace ("psphotBlendFit", 5, "source at %7.1f, %7.1f is ext", source->moments->x, source->moments->y);
+	    Next ++;
+	    continue;
+	}
+
+	psTrace ("psphotBlendFit", 5, "source at %7.1f, %7.1f failed", source->moments->x, source->moments->y);
+	Nfail ++;
+
+	// re-subtract PSF for object, leave local sky
+	pmModelSub (source->pixels, source->mask, source->modelPSF, false, false);
+	source->mode |= PM_SOURCE_MODE_SUBTRACTED;
+	source->mode |= PM_SOURCE_MODE_TEMPSUB;
+    }
+
+    psLogMsg ("psphot", 3, "fit models: %f sec for %d objects (%d psf, %d ext, %d failed, %ld skipped)\n", psTimerMark ("psphot"), Nfit, Npsf, Next, Nfail, sources->n - Nfit);
+    return (true);
+}
Index: /tags/RHL_v1_1/psphot/src/psphotChoosePSF.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotChoosePSF.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotChoosePSF.c	(revision 9533)
@@ -0,0 +1,215 @@
+# include "psphot.h"
+
+// try PSF models and select best option
+pmPSF *psphotChoosePSF (pmReadout *readout, psArray *sources, psMetadata *recipe) {
+
+    bool            status;
+    char           *modelName;
+    pmPSF          *psf = NULL;
+    pmPSFtry       *try = NULL;
+    psArray        *stars = NULL;
+
+    psTimerStart ("psphot");
+
+    // check if a PSF model is supplied by the user
+    psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    if (psf != NULL) return psf;
+
+    // examine PSF sources in S/N order (brightest first)
+    sources = psArraySort (sources, psphotSortBySN);
+
+    // array to store candidate PSF stars
+    int NSTARS = psMetadataLookupS32 (&status, recipe, "PSF_MAX_NSTARS");
+    if (!status) NSTARS = PS_MIN (sources->n, 200);
+
+    // use poissonian errors or local-sky errors
+    bool POISSON_ERRORS = psMetadataLookupBool (&status, recipe, "POISSON_ERRORS");
+    if (!status) POISSON_ERRORS = true;
+    pmSourceFitModelInit (15, 0.1, POISSON_ERRORS);
+
+    stars = psArrayAlloc (sources->n);
+
+    // select the candidate PSF stars (pointers to original sources)
+    for (int i = 0; (i < sources->n) && (stars->n < NSTARS); i++) {
+        pmSource *source = sources->data[i];
+        if (source->mode & PM_SOURCE_MODE_PSFSTAR) psArrayAdd (stars, 200, source);
+    }
+    psLogMsg ("psphot.pspsf", 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");
+
+    // get the list pointers for the PSF_MODEL entries
+    psList *list = NULL;
+    psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
+    if (mdi == NULL) psAbort ("psphotChoosePSF", "missing PSF_MODEL selection");
+
+    if (mdi->type == PS_DATA_STRING) {
+        list = psListAlloc(NULL);
+        psListAdd (list, PS_LIST_HEAD, mdi);
+    } else {
+        if (mdi->type != PS_DATA_METADATA_MULTI) psAbort ("psphotChoosePSF", "missing PSF_MODEL selection");
+        list = psMemIncrRefCounter(mdi->data.list);
+    }
+
+    // set up an array to store the results
+    psArray *models = psArrayAlloc (list->n);
+    models->n = list->n;
+
+    // try each model option listed in config
+    psListIterator *iter = psListIteratorAlloc (list, PS_LIST_HEAD, FALSE);
+    for (int i = 0; i < models->n; i++) {
+        psMetadataItem *item = psListGetAndIncrement (iter);
+        modelName = item->data.V;
+        models->data[i] = pmPSFtryModel (stars, modelName, RADIUS, POISSON_ERRORS);
+    }
+    psFree (iter);
+    psFree (list);
+    psFree (stars);
+
+    // select the best of the models
+    // here we are using the clippedStdev on the metric as the indicator
+    int bestN = -1;
+    float bestM = 0.0;
+    for (int i = 0; i < models->n; i++) {
+        try = models->data[i];
+	if (try == NULL) {
+	    psError(PSPHOT_ERR_PSF, false, "PSF model %d is NULL", i);
+	    continue;
+	}
+        float M = try->psf->dApResid;
+        if (bestN < 0 || M < bestM) {
+            bestM = M;
+            bestN = i;
+        }
+    }
+
+    // use the best model:
+    if (bestN < 0) {
+	psError(PSPHOT_ERR_PSF, false, "Failed to fit any models when choosing PSF");
+	psFree (models);
+	return NULL;
+    }
+    
+    try = models->data[bestN];
+
+    // unset the PSFSTAR flag for stars not used for PSF model
+    for (int i = 0; i < try->sources->n; i++) {
+        pmSource *source = try->sources->data[i];
+        if (try->mask->data.U8[i] & PSFTRY_MASK_EXT_FAIL) {
+            source->mode &= ~PM_SOURCE_MODE_PSFSTAR;
+        }
+    }
+
+    // save only the best model;
+    psf = psMemIncrRefCounter(try->psf);
+    psFree (models);
+
+    psMetadataAdd (recipe, PS_LIST_TAIL, "NPSFSTAR", PS_DATA_S32 | PS_META_REPLACE, "number of stars used to make PSF", psf->nPSFstars);
+
+    modelName = pmModelGetType (psf->type);
+    psLogMsg ("psphot.pspsf", 3, "select psf model: %f sec\n", psTimerMark ("psphot"));
+    psLogMsg ("psphot.pspsf", 3, "selected psf model %s, ApResid: %f +/- %f\n", modelName, psf->ApResid, psf->dApResid);
+
+    return (psf);
+}
+
+bool psphotPSFstats (pmReadout *readout, psMetadata *recipe, pmPSF *psf) {
+
+    psEllipseShape shape;
+    psEllipseAxes axes;
+
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(recipe, false);
+    PS_ASSERT_PTR_NON_NULL(psf, false);
+
+    psImage *image = readout->image;
+    PS_ASSERT_PTR_NON_NULL(image, false);
+
+    pmModel *modelEXT = pmModelAlloc (psf->type);
+    PS_ASSERT_PTR_NON_NULL(modelEXT, false);
+
+    // make a model with unit central intensity at the image center
+    modelEXT->params->data.F32[0] = 0;
+    modelEXT->params->data.F32[1] = 1;
+    modelEXT->params->data.F32[2] = 0.5*image->numCols;
+    modelEXT->params->data.F32[3] = 0.5*image->numRows;
+
+    // construct a PSF model at this coordinate
+    pmModel *modelPSF = pmModelFromPSF (modelEXT, psf);
+    PS_ASSERT_PTR_NON_NULL(modelPSF, false);
+
+    // get the correct model-radius function
+    pmModelRadius modelRadiusFunc = pmModelRadius_GetFunction (psf->type);
+
+    // get the model full-width at half-max
+    psF64 FWHM_X = 2*modelRadiusFunc (modelPSF->params, 0.5);
+
+    shape.sx  = modelPSF->params->data.F32[4];
+    shape.sy  = modelPSF->params->data.F32[5];
+    shape.sxy = modelPSF->params->data.F32[6];
+    axes = psEllipseShapeToAxes (shape);
+
+    psF64 FWHM_Y = FWHM_X * (axes.minor / axes.major);
+
+    psMetadataAdd (recipe, PS_LIST_TAIL, "FWHM_X", PS_DATA_F32 | PS_META_REPLACE, "PSF FWHM Major axis", FWHM_X);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "FWHM_Y", PS_DATA_F32 | PS_META_REPLACE, "PSF FWHM Minor axis", FWHM_Y);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "ANGLE",  PS_DATA_F32 | PS_META_REPLACE, "PSF angle",           axes.theta);
+
+    psFree (modelEXT);
+    psFree (modelPSF);
+
+    return true;
+}
+
+bool psphotMomentsStats (pmReadout *readout, psMetadata *recipe, psArray *sources) {
+
+    // without the PSF model, we can only rely on the source->moments
+    // as a measure of the FWHM values
+
+    int FWHM_N = 0;
+    double FWHM_X = 0.0;
+    double FWHM_Y = 0.0;
+    double FWHM_T = 0.0;
+
+    psEllipseMoments moments;
+    psEllipseAxes axes;
+
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(recipe, false);
+    PS_ASSERT_PTR_NON_NULL(sources, false);
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+	if (!source) continue;
+        if (!(source->mode & PM_SOURCE_MODE_PSFSTAR)) continue;
+
+        // moments->Sx,Sy are roughly sigma_x,y
+        moments.x2 = PS_SQR(source->moments->Sx);
+        moments.y2 = PS_SQR(source->moments->Sy);
+        moments.xy = source->moments->Sxy;
+
+        axes = psEllipseMomentsToAxes (moments);
+
+        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;
+
+    psMetadataAdd (recipe, PS_LIST_TAIL, "FWHM_X", PS_DATA_F32 | PS_META_REPLACE, "PSF FWHM Major axis", FWHM_X);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "FWHM_Y", PS_DATA_F32 | PS_META_REPLACE, "PSF FWHM Minor axis", FWHM_Y);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "ANGLE",  PS_DATA_F32 | PS_META_REPLACE, "PSF angle",           FWHM_T);
+
+    return true;
+}
Index: /tags/RHL_v1_1/psphot/src/psphotCleanup.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotCleanup.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotCleanup.c	(revision 9533)
@@ -0,0 +1,16 @@
+# include "psphot.h"
+
+void psphotCleanup (pmConfig *config) {
+
+    psFree (config);
+
+    psTimerStop ();
+    psMemCheckCorruption (true);
+    pmModelGroupCleanup ();
+    psTimeFinalize ();
+    pmConceptsDone ();
+    pmConfigDone ();
+    fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, NULL, false), "psphot");
+    // fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, stdout, false), "psphot");
+    return;
+}
Index: /tags/RHL_v1_1/psphot/src/psphotEnsemblePSF.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotEnsemblePSF.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotEnsemblePSF.c	(revision 9533)
@@ -0,0 +1,252 @@
+# include "psphot.h"
+bool pmSourceChisq (pmModel *model, psImage *image, psImage *mask, psImage *weight);
+
+// 2006.02.07 : no leaks!
+// fit all reasonable sources with the linear PSF model
+bool psphotEnsemblePSF (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf, bool final) { 
+
+    bool  status;
+    float x;
+    float y;
+    float f;
+    float r;
+
+    // psRegion allArray = psRegionSet (0, 0, 0, 0);
+
+    psTimerStart ("psphot");
+
+    // source analysis is done in spatial order
+    sources = psArraySort (sources, psphotSortByY);
+
+    // radius for source moments if star is saturated
+    // XXX we already do this in psphotSourceStats, is it still needed?
+    float INNER_RADIUS     = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
+
+    // setup the PSF fit details
+    psphotInitRadiusPSF (recipe, psf->type);
+
+    // pre-calculate all model pixels
+    psArray  *models = psArrayAlloc (sources->n);
+    psVector *index  = psVectorAlloc (sources->n, PS_TYPE_U32);
+    // DROP models->n = index->n = 0;
+
+    // option to limit analysis to a specific region
+    char *region = psMetadataLookupStr (&status, recipe, "ANALYSIS_REGION");
+    psRegion AnalysisRegion = psRegionFromString (region);
+    AnalysisRegion = psRegionForImage (readout->image, AnalysisRegion);
+    // psRegion AnalysisRegion = psRegionForImage (readout->image, psRegionFromString (region));
+    if (psRegionIsNaN (AnalysisRegion)) psAbort ("psphot", "analysis region mis-defined");
+
+    const bool CONSTANT_PHOTOMETRIC_WEIGHTS =
+	psMetadataLookupBool(&status, recipe, "CONSTANT_PHOTOMETRIC_WEIGHTS");
+    if (!status) {
+	psAbort(PS_FILE_LINE, "You must provide a value for the BOOL recipe CONSTANT_PHOTOMETRIC_WEIGHTS");
+    }
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *inSource = sources->data[i];
+
+	// skip non-astronomical objects (very likely defects)
+	// XXX EAM : should we try these anyway?
+	if (inSource->type == PM_SOURCE_TYPE_DEFECT) continue; 
+	if (inSource->type == PM_SOURCE_TYPE_SATURATED) continue;
+	if (final) {
+	    if (inSource->mode &  PM_SOURCE_MODE_SUBTRACTED) continue;
+	} else {
+	    if (inSource->mode &  PM_SOURCE_MODE_BLEND) continue;
+	}
+
+	if (inSource->moments->x < AnalysisRegion.x0) continue;
+	if (inSource->moments->y < AnalysisRegion.y0) continue;
+	if (inSource->moments->x > AnalysisRegion.x1) continue;
+	if (inSource->moments->y > AnalysisRegion.y1) continue;
+
+	pmSource *otSource = pmSourceAlloc ();
+
+	// really saturated stars should be re-measured for a better centroid
+	// XXX EAM : move this to a 'clear satstar function'
+	if (inSource->mode &  PM_SOURCE_MODE_SATSTAR) {
+	    status = pmSourceMoments (inSource, INNER_RADIUS);
+	}
+
+	// XXX EAM : add option to use EXT or PSF form
+	// use the source moments, etc to guess basic model parameters
+	pmModel *modelEXT = pmSourceModelGuess (inSource, psf->type); 
+	if (inSource->mode &  PM_SOURCE_MODE_SATSTAR) {
+	    modelEXT->params->data.F32[PM_PAR_XPOS] = inSource->moments->x;
+	    modelEXT->params->data.F32[PM_PAR_YPOS] = inSource->moments->y;
+	} else {
+	    // peak-up on peak (for non-sat objects)
+
+	    // ix,iy must land on inSource->pixels 
+	    int ix = PS_MAX (inSource->pixels->col0 + 1, PS_MIN (inSource->pixels->col0 + inSource->pixels->numCols - 2, inSource->peak->x));
+	    int iy = PS_MAX (inSource->pixels->row0 + 1, PS_MIN (inSource->pixels->row0 + inSource->pixels->numRows - 2, inSource->peak->y));
+
+	    psPolynomial2D *bicube = psImageBicubeFit (inSource->pixels, ix, iy);
+	    psPlane min = psImageBicubeMin (bicube);
+
+	    psTrace ("psphotEnsemblePSF", 5, "peak coord: %f %f -> %f %f\n", 
+		     modelEXT->params->data.F32[PM_PAR_XPOS], modelEXT->params->data.F32[PM_PAR_YPOS], min.x + ix, min.y + iy);
+	    
+	    // if min point is too deviant, keep the old value
+	    if ((fabs(min.x) < 1.5) && (fabs(min.y) < 1.5)) {
+		modelEXT->params->data.F32[PM_PAR_XPOS] = min.x + ix;
+		modelEXT->params->data.F32[PM_PAR_YPOS] = min.y + iy;
+	    }
+	    psFree (bicube);
+	}
+
+	// set PSF parameters for this model
+	pmModel *model = pmModelFromPSF (modelEXT, psf);
+	psFree (modelEXT);
+
+	// save the original coords
+	x = model->params->data.F32[PM_PAR_XPOS];
+	y = model->params->data.F32[PM_PAR_YPOS];
+
+	// set the fit radius based on the object flux limit and the model
+	psphotCheckRadiusPSF (readout, inSource, model);
+
+	// make temporary copies of the image pixels and mask
+	otSource->mask   = psImageCopy (NULL, inSource->mask,   PS_TYPE_U8);
+	otSource->pixels = psImageCopy (NULL, inSource->pixels, PS_TYPE_F32);
+	otSource->weight = psImageCopy (NULL, inSource->weight, PS_TYPE_F32);
+
+	// build the model image 
+	psImage *flux = otSource->pixels;
+	psImage *mask = otSource->mask;
+
+	// set model to unit peak, zero sky (we assume sky is subtracted)
+	model->params->data.F32[PM_PAR_SKY] = 0.0;
+	model->params->data.F32[PM_PAR_I0] = 1.0;
+
+	// fill in the model pixel values
+	psImageInit (flux, 0.0);
+	psImageKeepCircle (mask, x, y, model->radiusTMP, "OR", PM_MASK_MARK);
+	pmModelAdd (flux, mask, model, false, false);
+
+	// calculate nDOF (nPix - 1)
+	// int Nmaskpix = psImageCountPixelMask (mask, allArray, PM_MASK_SAT);
+	// model->nDOF  = mask->numCols*mask->numRows - Nmaskpix - 1;
+
+	// save source in list
+	otSource->modelPSF = model;
+	index->data.U32[models->n] = i;
+	psArrayAdd (models, 100, otSource);
+	psFree (otSource);
+    }
+    psLogMsg ("psphot.emsemble", 4, "built models: %f (%ld objects)\n", psTimerMark ("psphot"), sources->n);
+    
+    // fill out the sparse matrix
+    psSparse *sparse = psSparseAlloc (models->n, 100);
+    psVector *weight = psVectorAlloc (models->n, PS_TYPE_F32);
+    weight->n = models->n;
+
+    for (int i = 0; i < models->n; i++) {
+	int N = index->data.U32[i];
+	pmSource *Fi = sources->data[N];
+	pmSource *Mi = models->data[i];
+
+	// scale by diagonal element (auto-cross-product)
+	r = pmSourceCrossProduct (Mi, Mi, CONSTANT_PHOTOMETRIC_WEIGHTS);
+	weight->data.F32[i] = r;
+
+	psSparseMatrixElement (sparse, i, i, 1.0);
+
+	// find the image x model value
+	f = pmSourceCrossProduct (Fi, Mi, CONSTANT_PHOTOMETRIC_WEIGHTS);
+	psSparseVectorElement (sparse, i, f / r);
+
+	// loop over all other stars following this one
+	for (int j = i + 1; j < models->n; j++) {
+	    pmSource *Mj = models->data[j];
+
+	    // skip over disjoint source images, break after last possible overlap
+	    if (Mi->pixels->row0 + Mi->pixels->numRows < Mj->pixels->row0) break;
+	    if (Mj->pixels->row0 + Mj->pixels->numRows < Mi->pixels->row0) continue;
+	    if (Mi->pixels->col0 + Mi->pixels->numCols < Mj->pixels->col0) continue;
+	    if (Mj->pixels->col0 + Mj->pixels->numCols < Mi->pixels->col0) continue;
+	    
+	    // got an overlap; calculate cross-product and add to output array
+	    f = pmSourceCrossProduct (Mi, Mj, CONSTANT_PHOTOMETRIC_WEIGHTS);
+	    psSparseMatrixElement (sparse, j, i, f / r); 
+	}
+    }
+    psLogMsg ("psphot.emsemble", 4, "built matrix: %f (%d elements)\n", psTimerMark ("psphot"), sparse->Nelem);
+
+    psSparseConstraint constraint;
+    constraint.paramMin   = 0.0;
+    constraint.paramMax   = 1e8;
+    constraint.paramDelta = 1e8;
+
+    // solve for normalization terms (need include local sky?)
+    psSparseResort (sparse);
+    psVector *norm = psSparseSolve (NULL, constraint, sparse, 3);
+
+    // adjust models, set sources and subtract
+    for (int i = 0; i < models->n; i++) {
+	int N = index->data.U32[i];
+	pmSource *Fi = sources->data[N];
+	pmSource *Mi = models->data[i];
+
+	// if we already have a PSF model, free it.
+	psFree (Fi->modelPSF);
+
+	// need to increment counter so we can free models here and sources above
+	Fi->modelPSF = psMemIncrRefCounter (Mi->modelPSF);
+
+	// assign linearly-fitted normalization
+	if (isnan(norm->data.F32[i])) {
+	    psAbort ("psphot", "ensemble source is nan");
+	}
+	Fi->modelPSF->params->data.F32[PM_PAR_I0] = norm->data.F32[i];
+	Fi->modelPSF->dparams->data.F32[PM_PAR_I0] = sqrt(sqrt(2) * norm->data.F32[i] / (sparse->Bfj->data.F32[i] * weight->data.F32[i]));
+	// XXX EAM : this factor of sqrt(2) makes the errors consistent, but I don't understand it
+
+	// subtract object
+	pmModelSub (Fi->pixels, Fi->mask, Fi->modelPSF, false, false);
+	Fi->mode |= PM_SOURCE_MODE_SUBTRACTED;
+	if (!final) Fi->mode |= PM_SOURCE_MODE_TEMPSUB;
+    }
+
+    // measure chisq for each source
+    for (int i = 0; final && (i < models->n); i++) {
+	int N = index->data.U32[i];
+	pmSource *Fi = sources->data[N];
+	pmModel *model = Fi->modelPSF;
+
+	x = model->params->data.F32[PM_PAR_XPOS];
+	y = model->params->data.F32[PM_PAR_YPOS];
+
+	psImageKeepCircle (Fi->mask, x, y, model->radiusTMP, "OR", PM_MASK_MARK);
+	pmSourceChisq (model, Fi->pixels, Fi->mask, Fi->weight);
+	psImageKeepCircle (Fi->mask, x, y, model->radiusTMP, "AND", NOT_U8(PM_MASK_MARK));
+    }
+
+    psFree (index);
+    psFree (sparse);
+    psFree (models);
+    psFree (norm);
+    psFree (weight);
+
+    psLogMsg ("psphot.emsemble", 3, "measure ensemble of PSFs: %f sec\n", psTimerMark ("psphot"));
+    return true;
+}
+
+bool pmSourceChisq (pmModel *model, psImage *image, psImage *mask, psImage *weight) {
+
+    double dC = 0.0;
+    int Npix = 0;
+    for (int j = 0; j < image->numRows; j++) {
+	for (int i = 0; i < image->numCols; i++) {
+	    if (mask->data.U8[j][i]) continue;
+	    if (weight->data.F32[j][i] <= 0) continue;
+	    dC += PS_SQR (image->data.F32[j][i]) / weight->data.F32[j][i];
+	    Npix ++;
+	}
+    }
+    model->nDOF = Npix - 1;
+    model->chisq = dC;
+
+    return (true);
+}
Index: /tags/RHL_v1_1/psphot/src/psphotErrorCodes.c.in
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotErrorCodes.c.in	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotErrorCodes.c.in	(revision 9533)
@@ -0,0 +1,26 @@
+/*
+ * 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));
+       p_psMemSetPersistent(tmp, true);
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /tags/RHL_v1_1/psphot/src/psphotErrorCodes.dat
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotErrorCodes.dat	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotErrorCodes.dat	(revision 9533)
@@ -0,0 +1,13 @@
+#
+# This file is used to generate pmErrorClasses.h
+#
+BASE = 512		First value we use; lower values belong to psLib
+UNKNOWN			Unknown PM error code
+NOT_IMPLEMENTED		Desired feature is not yet implemented
+ARGUMENTS		Incorrect arguments
+CONFIG			Problem in configure files
+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
Index: /tags/RHL_v1_1/psphot/src/psphotErrorCodes.h.in
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotErrorCodes.h.in	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotErrorCodes.h.in	(revision 9533)
@@ -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 = 512,
+    PSPHOT_ERR_${ErrorCode},
+    PSPHOT_ERR_NERROR
+} actErrorCode;
+
+void psphotErrorRegister(void);
+
+#endif
Index: /tags/RHL_v1_1/psphot/src/psphotEvalFLT.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotEvalFLT.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotEvalFLT.c	(revision 9533)
@@ -0,0 +1,48 @@
+# include "psphot.h"
+
+bool psphotEvalEXT (pmSource *source, pmModel *model)
+{ 
+    int keep;
+
+    // do we actually have a valid EXT model?
+    if (model == NULL) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED;
+	return false;
+    }
+
+    // did the model fit fail for one or another reason?
+    switch (model->status) {
+      case PM_MODEL_SUCCESS:
+	break;
+      case PM_MODEL_UNTRIED:
+	source->mode &= ~PM_SOURCE_MODE_FITTED; 
+	return false;
+      case PM_MODEL_BADARGS:
+      case PM_MODEL_NONCONVERGE:
+      case PM_MODEL_OFFIMAGE:
+      default:
+	psLogMsg ("psphot", 5, "EXT fail fit\n");
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    }
+
+    // unless we prove otherwise, this object is extended
+    source->type = PM_SOURCE_TYPE_EXTENDED;
+
+    // the following source->mode information pertains to modelEXT:
+    source->mode |= PM_SOURCE_MODE_EXTMODEL;
+
+    // if the object has a fitted peak below 0, the fit did not converge cleanly
+    if (model->params->data.F32[1] <= 0) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    } 
+
+    keep = pmModelFitStatus (model);
+    if (keep) return true;
+
+    // poor-quality fit; only keep if nothing else works...
+    psLogMsg ("psphot", 5, "EXT poor fit\n");
+    source->mode |= PM_SOURCE_MODE_POOR;
+    return false;
+}	
Index: /tags/RHL_v1_1/psphot/src/psphotEvalPSF.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotEvalPSF.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotEvalPSF.c	(revision 9533)
@@ -0,0 +1,207 @@
+# include "psphot.h"
+
+// given a pmSource which has been fitted using modelPSF, evaluate the
+// resulting fit: did the fit succeed? is this object PSF-like? is this object 
+// extended?  return status is TRUE for a valid PSF, FALSE otherwise.
+
+// identify objects consistent with PSF shape/magnitude distribution
+// we expect dparams[4],dparams[5] to have a scatter of:
+// sigma_x / (S/N) * sqrt(2)
+// 1 / (params[4],params[5])*(S/N)
+
+// sigma_x : 1 / SX
+// dsx : 1 / (SX * SN)
+// dsx_o = hypot (1/(SX*SN), MIN_DSX)
+
+// any objects which is consistent with the PSF should have 
+// abs(dparams[5]) < N * dsxLine(mag) & abs(dparams[6]) < N * dsyLine(mag)
+// this includes a minimum buffer (DS) for the brighter objects
+
+// saturated stars should fall outside (larger), but have peaks above SATURATION
+// extended sources should be larger, cosmic rays smaller
+// we also reject objects with S/N too low or ChiSquare to high
+
+// floor for DS value 
+// XXX EAM : add to configuration?
+# define MIN_DS 0.01
+
+static float SATURATION;
+static float PSF_MIN_SN;
+static float PSF_MAX_CHI;
+static float PSF_SHAPE_NSIGMA;
+
+bool psphotInitLimitsPSF (psMetadata *recipe) {
+
+    bool status;
+
+    // we may set this differently here from the value used to mark likely saturated stars
+    SATURATION       = psMetadataLookupF32 (&status, recipe, "SATURATION");
+    PSF_MIN_SN       = psMetadataLookupF32 (&status, recipe, "PSF_MIN_SN");
+    PSF_MAX_CHI      = psMetadataLookupF32 (&status, recipe, "PSF_MAX_CHI");
+    PSF_SHAPE_NSIGMA = psMetadataLookupF32 (&status, recipe, "PSF_SHAPE_NSIGMA");
+
+    return true;
+}
+
+// examine the model->status, fit parameters, etc and decide if the model succeeded
+// set the source->type and source->mode appropriately
+bool psphotEvalPSF (pmSource *source, pmModel *model) { 
+
+    int keep;
+    float dSX, dSY, SX, SY, SN;
+    float nSx, nSy, Chi;
+
+    // do we actually have a valid PSF model?
+    if (model == NULL) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED;
+	return false;
+    }
+
+    // did the model fit fail for one or another reason?
+    switch (model->status) {
+      case PM_MODEL_SUCCESS:
+	break;
+      case PM_MODEL_UNTRIED:
+	source->mode &= ~PM_SOURCE_MODE_FITTED; 
+	return false;
+      case PM_MODEL_BADARGS:
+      case PM_MODEL_NONCONVERGE:
+      case PM_MODEL_OFFIMAGE:
+      default:
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    }
+
+    // unless we prove otherwise, this object is a star.
+    source->type = PM_SOURCE_TYPE_STAR;
+
+    // the following source->mode information pertains to modelPSF:
+    source->mode |= PM_SOURCE_MODE_PSFMODEL;
+
+    // if the object has fitted peak above saturation, label as SATSTAR
+    // this is a valid PSF object, but ignore the other quality tests
+    // remember: fit does not use saturated pixels (masked)
+    // XXX no extended object can saturate and stay extended...
+    if (model->params->data.F32[1] >= 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[1] <= 0) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    } 
+
+    // if the source was predicted to be a SATSTAR, but it fitted below saturation, 
+    // make a note to the user
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	psLogMsg ("psphot", 5, "SATSTAR marked normal (fitted peak below saturation)\n");
+	source->mode &= ~PM_SOURCE_MODE_SATSTAR;
+    }
+
+    SN  = model->params->data.F32[1]/model->dparams->data.F32[1];
+    SX  = model->params->data.F32[4];
+    SY  = model->params->data.F32[5];
+    dSX = model->dparams->data.F32[4];
+    dSY = model->dparams->data.F32[5];
+    Chi = model->chisqNorm / model->nDOF;
+
+    // swing of sigma_x,y in sigmas
+    nSx = dSX / hypot (MIN_DS, 1 / (SX * SN));
+    nSy = dSY / hypot (MIN_DS, 1 / (SY * SN));
+
+    // assign PM_SOURCE_MODE_GOODSTAR to bright objects within PSF region of dparams[]
+    keep = TRUE;
+    keep &= (fabs(nSx) < PSF_SHAPE_NSIGMA);
+    keep &= (fabs(nSy) < PSF_SHAPE_NSIGMA);
+    keep &= (SN > PSF_MIN_SN);
+    keep &= (Chi < PSF_MAX_CHI);
+    if (keep) return true;
+
+    // this source is not a star, warn if it was a PSFSTAR
+    if (source->mode & PM_SOURCE_MODE_PSFSTAR) {
+	psLogMsg ("psphot", 5, "PSFSTAR demoted based on fit quality   (%f, %f  :  %f %f %f %f)\n", 
+		  model->params->data.F32[2], model->params->data.F32[3], nSx, nSy, SN, Chi);
+    } else {
+	psLogMsg ("psphot", 5, "fails PSF fit (%f, %f  :  %f %f %f %f)\n", 
+		  model->params->data.F32[2], model->params->data.F32[3], nSx, nSy, SN, Chi);
+    }
+
+    // object appears to be small, suspected defect
+    if ((nSx <= -PSF_SHAPE_NSIGMA) || (nSy <= -PSF_SHAPE_NSIGMA)) {
+	source->type = PM_SOURCE_TYPE_DEFECT;
+	return false;
+    }
+
+    // object appears to be large, suspected extended source
+    if ((nSx >= PSF_SHAPE_NSIGMA) || (nSy >= PSF_SHAPE_NSIGMA)) {
+	source->type = PM_SOURCE_TYPE_EXTENDED;
+	return false;
+    }
+
+    // poor-quality fit; only keep if nothing else works...
+    source->mode |= PM_SOURCE_MODE_POOR;
+    return false;
+}	
+
+// examine the model->status, fit parameters, etc and decide if the model succeeded
+// set the source->type and source->mode appropriately
+bool psphotEvalDBL (pmSource *source, pmModel *model) { 
+
+    // do we actually have a valid PSF model?
+    if (model == NULL) {
+	source->mode &= ~PM_SOURCE_MODE_FITTED;
+	return false;
+    }
+
+    // did the model fit fail for one or another reason?
+    switch (model->status) {
+      case PM_MODEL_SUCCESS:
+	break;
+      case PM_MODEL_UNTRIED:
+	source->mode &= ~PM_SOURCE_MODE_FITTED; 
+	return false;
+      case PM_MODEL_BADARGS:
+      case PM_MODEL_NONCONVERGE:
+      case PM_MODEL_OFFIMAGE:
+      default:
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    }
+
+    // unless we prove otherwise, this object is a star.
+    source->type = PM_SOURCE_TYPE_STAR;
+
+    // the following source->mode information pertains to modelPSF:
+    source->mode |= PM_SOURCE_MODE_PSFMODEL;
+
+    // if the object has fitted peak above saturation, label as SATSTAR
+    // this is a valid PSF object, but ignore the other quality tests
+    // remember: fit does not use saturated pixels (masked)
+    // XXX no extended object can saturate and stay extended...
+    if (model->params->data.F32[1] >= 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[1] <= 0) {
+	source->mode |= PM_SOURCE_MODE_FAIL;
+	return false;
+    } 
+
+    // if the source was predicted to be a SATSTAR, but it fitted below saturation, 
+    // make a note to the user
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	psLogMsg ("psphot", 5, "SATSTAR marked normal (fitted peak below saturation)\n");
+	source->mode &= ~PM_SOURCE_MODE_SATSTAR;
+    }
+    return true;
+}	
Index: /tags/RHL_v1_1/psphot/src/psphotFakeSources.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotFakeSources.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotFakeSources.c	(revision 9533)
@@ -0,0 +1,31 @@
+# include "psphot.h"
+
+psArray *psphotFakeSources () {
+
+    // psphotUpdateHeader (header, config);
+
+    psArray *sources = psArrayAlloc (50);
+    sources->n = 50;
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = pmSourceAlloc ();
+	source->moments = pmMomentsAlloc ();
+	source->moments->x = 10;
+	source->moments->y = 10;
+	source->moments->Sx = 1;
+	source->moments->Sy = 1;
+	source->moments->Sxy = 0;
+	source->moments->Sum = 1000;
+	source->moments->Peak = 100;
+	source->moments->Sky = 10;
+	source->moments->nPixels = 10;
+
+	source->peak = pmPeakAlloc (10, 10, 0, 0);
+	source->type = PM_SOURCE_TYPE_STAR;
+
+	pmModelType modelType = pmModelSetType ("PS_MODEL_QGAUSS");
+	source->modelPSF = pmSourceModelGuess (source, modelType);
+	sources->data[i] = source;
+    }
+    return sources;
+}
Index: /tags/RHL_v1_1/psphot/src/psphotFindPeaks.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotFindPeaks.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotFindPeaks.c	(revision 9533)
@@ -0,0 +1,72 @@
+# include "psphot.h"
+
+// 2006.02.02 : no leaks
+psArray *psphotFindPeaks (pmReadout *readout, psMetadata *recipe) {
+
+    bool  status = false;
+    float NSIGMA;
+    float SIGMA;
+    float threshold;
+    float value;
+
+    // smooth the image and weight map
+    psTimerStart ("psphot");
+
+    SIGMA  = psMetadataLookupF32 (&status, recipe, "PEAKS_SMOOTH_SIGMA");
+    NSIGMA = psMetadataLookupF32 (&status, recipe, "PEAKS_SMOOTH_NSIGMA");
+
+    // XXX we are somewhat doing the wrong thing here with the mask.
+    // it would be better to smooth the image using the mask information
+    psImage *smooth_im = psImageCopy (NULL, readout->image, PS_TYPE_F32);
+    psImageSmooth (smooth_im, SIGMA, NSIGMA);
+    psLogMsg ("psphot", 4, "smooth image: %f sec\n", psTimerMark ("psphot"));
+
+    psImage *smooth_wt = psImageCopy (NULL, readout->weight, PS_TYPE_F32);
+    psImageSmooth (smooth_wt, SIGMA, NSIGMA);
+    psLogMsg ("psphot", 4, "smooth weight: %f sec\n", psTimerMark ("psphot"));
+
+    psImage *mask = readout->mask;
+
+    // XXX make this a user-option?
+    // psphotSaveImage (NULL, smooth_im, "imsmooth.fits");
+    // psphotSaveImage (NULL, smooth_wt, "wtsmooth.fits");
+
+    psTimerStart ("psphot");
+    for (int j = 0; j < smooth_im->numRows; j++) {
+	for (int i = 0; i < smooth_im->numCols; i++) {
+	    value = smooth_im->data.F32[j][i];
+	    smooth_im->data.F32[j][i] = 0.0;
+	    if (mask->data.U8[j][i]) continue;
+	    if (smooth_wt->data.F32[j][i] <= 0) continue;
+	    smooth_im->data.F32[j][i] = PS_SQR(value) / smooth_wt->data.F32[j][i];
+	}
+    }
+    psLogMsg ("psphot", 4, "built S/N map: %f sec\n", psTimerMark ("psphot"));
+
+    psTimerStart ("psphot");
+
+    // set peak threshold
+    NSIGMA = psMetadataLookupF32 (&status, recipe, "PEAKS_NSIGMA_LIMIT");
+    // threshold = NSIGMA*sky->sampleStdev + sky->sampleMean; 
+    threshold = PS_SQR(NSIGMA);
+    psLogMsg ("psphot", 4, "threshold: %f DN\n", threshold);
+
+    // find the peaks in the smoothed image 
+    psArray *peaks = pmFindImagePeaks (smooth_im, threshold);
+    if (peaks == NULL) psAbort ("find peaks", "no peaks found");
+    psFree (smooth_im);
+    psFree (smooth_wt);
+
+    // optional dump of all peak data
+    char *output = psMetadataLookupStr (&status, recipe, "PEAKS_OUTPUT_FILE");
+    if (status && (output != NULL) && (output[0])) {
+	pmPeaksWriteText (peaks, output);
+    }
+    psLogMsg ("psphot", 3, "%ld peaks: %f sec\n", peaks->n, psTimerMark ("psphot"));
+
+    return (peaks);
+}
+
+// In this function, we smooth the image, then search for the peaks 
+// Should we also subtract a super-binned image? (as an option?)
+// XXX : We need to gracefully handle no source detections
Index: /tags/RHL_v1_1/psphot/src/psphotFitSet.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotFitSet.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotFitSet.c	(revision 9533)
@@ -0,0 +1,47 @@
+# include "psphot.h"
+
+bool psphotFitSet (pmSource *source, pmModel *oneModel, char *fitset, pmSourceFitMode mode) {
+
+    double x, y, Io;
+
+    FILE *f = fopen (fitset, "r");
+    if (f == NULL) return false;
+
+    psArray *modelSet = psArrayAlloc (16);
+    // DROP modelSet->n = 0;
+
+    while (fscanf (f, "%lf %lf %lf", &x, &y, &Io) == 3) {
+	pmModel *model = pmModelAlloc (oneModel->type);
+	
+	for (psS32 i = 0; i < model->params->n; i++) {
+	    model->params->data.F32[i] = oneModel->params->data.F32[i];
+	    model->dparams->data.F32[i] = oneModel->dparams->data.F32[i];
+	}
+	model->params->data.F32[1] = Io;
+	model->params->data.F32[2] = x;
+	model->params->data.F32[3] = y;
+	psArrayAdd (modelSet, 16, model);
+    }
+
+    pmSourceFitSet (source, modelSet, mode);
+
+    // write out positive object
+    psphotSaveImage (NULL, source->pixels, "object.fits");
+
+    // subtract object, leave local sky
+    for (int i = 0; i < modelSet->n; i++) {
+	pmModel *model = modelSet->data[i];
+	pmModelSub (source->pixels, source->mask, model, false, false);
+    
+	fprintf (stderr, "output parameters (obj %d):\n", i);
+	for (int n = 0; n < model->params->n; n++) {
+	    fprintf (stderr, "%d : %f\n", n, model->params->data.F32[n]);
+	}
+    }
+
+    // write out 
+    psphotSaveImage (NULL, source->pixels, "resid.fits");
+    psphotSaveImage (NULL, source->mask, "mask.fits");
+    return true;
+}
+
Index: /tags/RHL_v1_1/psphot/src/psphotGrowthCurve.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotGrowthCurve.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotGrowthCurve.c	(revision 9533)
@@ -0,0 +1,72 @@
+# include "psphot.h"
+
+// XXX we can probably move this into pmGrowthCurve.c
+// XXX need to change the way we grab an image, or else use
+//     the 'center' option
+
+bool psphotGrowthCurve (pmReadout *readout, pmPSF *psf) { 
+
+    // bool status;
+    float xc, yc, dx, dy;
+    float fitMag, apMag;
+    float radius;
+
+    // create template model
+    pmModel *modelRef = pmModelAlloc(psf->type);
+
+    // use center of the image 
+    xc = 0.5*readout->image->numCols + readout->image->col0;
+    yc = 0.5*readout->image->numRows + readout->image->row0;
+    dx = psf->growth->maxRadius + 1;
+    dy = psf->growth->maxRadius + 1;
+
+    // assign the x and y coords to the image center
+    // create an object with center intensity of 1000
+    modelRef->params->data.F32[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);
+
+    // generate working image for this source
+    psRegion region = {xc - dx, xc + dx, yc - dy, yc + dy};
+    psImage *view = psImageSubset (readout->image, region);
+    psImage *image = psImageCopy (NULL, view, PS_TYPE_F32);
+    psImage *mask = psImageCopy (NULL, view, PS_TYPE_U8);
+    psImageInit (mask, 0);
+
+    // loop over a range of source fluxes
+    for (int i = 0; i < psf->growth->radius->n; i++) {
+
+	psImageInit (image, 0.0);
+
+	radius = psf->growth->radius->data.F32[i];
+
+	psImageKeepCircle (mask, xc, yc, radius, "OR", PM_MASK_MARK);
+
+	pmModelAdd (image, mask, model, false, false);
+
+	pmSourcePhotometryAper (&apMag, model, image, mask);
+
+	psImageKeepCircle (mask, xc, yc, radius, "AND", NOT_U8(PM_MASK_MARK));
+	psf->growth->apMag->data.F32[i] = apMag;
+    }
+
+    pmSourcePhotometryModel (&fitMag, model);
+    psf->growth->fitMag = fitMag;
+
+    psf->growth->apRef = psVectorInterpolate (psf->growth->radius, psf->growth->apMag, psf->growth->refRadius);
+    psf->growth->apLoss = psf->growth->fitMag - psf->growth->apRef;
+
+    psLogMsg ("psphot.growth", 4, "GrowthCurve : apLoss : %f\n", psf->growth->apLoss);
+    
+    psFree (view);
+    psFree (image);
+    psFree (mask);
+    psFree (model);
+    psFree (modelRef);
+
+    return true;
+}
Index: /tags/RHL_v1_1/psphot/src/psphotImageLoop.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotImageLoop.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotImageLoop.c	(revision 9533)
@@ -0,0 +1,98 @@
+# include "psphot.h"
+
+// XXX where do we load optional mask and weight input images?
+bool psphotImageLoop (pmConfig *config) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT");
+    if (!status) {
+	psError(PS_ERR_IO, false, "Can't find input data!");
+	return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+	psError(PS_ERR_UNKNOWN, false, "failed IO for fpa in psphot\n");
+	psFree(view);
+	return false;
+    }
+
+    while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+        psLogMsg ("psphot", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (! chip->process || ! chip->file_exists) { continue; }
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+            psError(PS_ERR_UNKNOWN, false, "failed IO for chip %d in psphot\n", view->chip);
+	    psFree (view);
+	    return false;
+	}
+	
+	while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psLogMsg ("psphot", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (! cell->process || ! cell->file_exists) { continue; }
+	    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+		psError(PS_ERR_UNKNOWN, false, "failed IO for chip %d, cell %d in psphot\n", view->chip, view->cell);
+		psFree (view);
+		return false;
+	    }
+	    
+	    // process each of the readouts
+	    while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+		if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+		    psError(PS_ERR_UNKNOWN, false, "failed IO for chip %d, cell %d, readout %d in psphot\n", view->chip, view->cell, view->readout);
+		    psFree (view);
+		    return false;
+		}
+		
+		if (! readout->data_exists) { continue; }
+		
+		// run the actual photometry analysis
+		if (!psphotReadout (config, view)) {
+		    psError(PS_ERR_UNKNOWN, false, "failed psphotReadout for chip %d, cell %d, readout %d\n", view->chip, view->cell, view->readout);
+		    psFree (view);
+		    return false;
+		}
+		
+		if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) {
+		    psError(PS_ERR_UNKNOWN, false, "failed IO for chip %d, cell %d, readout %d in psphot\n", view->chip, view->cell, view->readout);
+		    psFree (view);
+		    return false;
+		}
+	    }
+	    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) {
+		psError(PS_ERR_UNKNOWN, false, "failed IO for chip %d, cell %d in psphot\n", view->chip, view->cell);
+		psFree (view);
+		return false;
+	    }
+	}
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) {
+            psError(PS_ERR_UNKNOWN, false, "failed IO for chip %d in psphot\n", view->chip);
+	    psFree (view);
+	    return false;
+	}
+    }
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) {
+	psError(PS_ERR_UNKNOWN, false, "failed IO for fpa in psphot\n");
+	psFree (view);
+	return false;
+    }
+    
+    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
+
Index: /tags/RHL_v1_1/psphot/src/psphotImageMedian.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotImageMedian.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotImageMedian.c	(revision 9533)
@@ -0,0 +1,192 @@
+# include "psphot.h"
+
+// generate the median in NxN boxes, clipping heavily
+// linear interpolation to generate full-scale model
+bool psphotImageMedian (pmConfig *config, pmFPAview *view)
+{
+    bool status;
+    pmFPA *inFPA;
+    pmFPAfile *file;
+    psRegion region;
+    int MAX_SAMPLE_PIXELS;
+
+    pmReadout *model = NULL;
+    pmReadout *readout = NULL;
+    pmReadout *background = NULL;
+    pmReadout *backSub = NULL;
+
+    psTimerStart ("psphot");
+
+    // select the appropriate recipe information
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, "PSPHOT");
+
+    MAX_SAMPLE_PIXELS = psMetadataLookupF32 (&status, recipe, "IMSTATS_NPIX");
+    if (!status) {
+        MAX_SAMPLE_PIXELS = 1000;
+    }
+    unsigned long seed = psMetadataLookupS32 (&status, recipe, "IMSTATS_SEED");
+    if (!status) {
+        seed = 0;
+    }
+    psRandom *rng = psRandomAlloc(PS_RANDOM_TAUS, seed);
+
+    // subtract this amount extra from the sky
+    float SKY_BIAS = psMetadataLookupF32 (&status, recipe, "SKY_BIAS");
+    if (!status) {
+        SKY_BIAS = 0;
+    }
+
+    // find the currently selected readout
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT");
+    inFPA = file->fpa;
+    readout = pmFPAviewThisReadout (view, inFPA);
+
+    psImage *image = readout->image;
+    psImage *mask  = readout->mask;
+
+    // dimensions of input & output image
+    int Nx = image->numCols;
+    int Ny = image->numRows;
+
+    // scaling factor
+    int DX = psMetadataLookupS32 (&status, recipe, "BACKGROUND.XBIN");
+    int DY = psMetadataLookupS32 (&status, recipe, "BACKGROUND.YBIN");
+
+    // overhang : we will balance this evenly
+    int dx = (Nx % DX) / 2;
+    int dy = (Ny % DY) / 2;
+
+    // dimensions of binned image
+    int nx = (Nx % DX) ? (int)(Nx / DX) + 1 : Nx / DX;
+    int ny = (Ny % DY) ? (int)(Ny / DY) + 1 : Ny / DY;
+
+    // select model pixels (from output background model file, or create internal file)
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKMDL");
+    if (file == NULL) {
+	// we are not using PSPHOT.BACKMDL as an I/O file: define an internal version
+        model = pmFPAfileDefineInternal (config->files, "PSPHOT.BACKMDL", nx, ny, PS_TYPE_F32);
+    } else {
+	// we are using PSPHOT.BACKMDL as an I/O file: select readout or create
+	model = pmFPAviewThisReadout (view, file->fpa);
+	if (model == NULL) {
+	    // readout does not yet exist: create from input
+	    pmFPAfileCopyStructureView (file->fpa, inFPA, DX, DY, view);
+	    model = pmFPAviewThisReadout (view, file->fpa);
+	    if (nx != model->image->numCols) psAbort ("psphot", "inconsistent size");
+	    if (ny != model->image->numRows) psAbort ("psphot", "inconsistent size");
+	}
+    }
+    psF32 **modelData = model->image->data.F32;
+    assert(model->analysis != NULL);
+    psMetadataAdd(model->analysis, PS_LIST_TAIL, "XBIN", PS_DATA_S32 | PS_META_REPLACE,
+		  "Background x-binsize", DX);
+    psMetadataAdd(model->analysis, PS_LIST_TAIL, "YBIN", PS_DATA_S32 | PS_META_REPLACE,
+		  "Background x-binsize", DY);
+    psMetadataAdd(model->analysis, PS_LIST_TAIL, "x0", PS_DATA_S32 | PS_META_REPLACE,
+		  "Background x-overhang", dx);
+    psMetadataAdd(model->analysis, PS_LIST_TAIL, "y0", PS_DATA_S32 | PS_META_REPLACE,
+		  "Background y-overhang", dy);
+
+    // measure clipped median for subimages
+    for (int iy = 0; iy < ny; iy++) {
+        for (int ix = 0; ix < nx; ix++) {
+            // sx, sy are in parent coords
+            int sx = ix*DX - dx + image->col0;
+            int sy = iy*DY - dy + image->row0;
+            region = psRegionSet (sx, sx + 2*DX, sy, sy + 2*DY);
+            region = psRegionForImage (image, region);
+            psImage *subset  = psImageSubset (image, region);
+	    if (!subset->numCols || !subset->numRows) {
+		psFree (subset);
+		continue;
+	    }
+
+            psImage *submask = psImageSubset (mask, region);
+
+            // XXX the value of the upper and lower cuts probably should be studied...
+            psStats *stats = psImageBackground(subset, submask, 0xff, 0.25, 0.75, MAX_SAMPLE_PIXELS, rng);
+            modelData[iy][ix] = stats->robustMedian + SKY_BIAS;
+
+            psFree (stats);
+            psFree (subset);
+            psFree (submask);
+
+            // XXX psImageStats is still very inefficient and poorly coded...
+            // psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN);
+            // stats = psImageStats (stats, subset, maskset, 0xff);
+            // backMdl->data.F32[iy][ix] = stats->clippedMean;
+        }
+    }
+    psLogMsg ("psphot", 3, "build median image: %f sec\n", psTimerMark ("psphot"));
+
+    // measure background stats and save for later output
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV | PS_STAT_MIN | PS_STAT_MAX);
+    stats = psImageStats (stats, model->image, NULL, 0);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_MEAN", PS_DATA_F32 | PS_META_REPLACE, "sky model mean",          stats->sampleMean);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_STDV", PS_DATA_F32 | PS_META_REPLACE, "sky model stdev",         stats->sampleStdev);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_MAX",  PS_DATA_F32 | PS_META_REPLACE, "sky model maximum value", stats->max);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_MIN",  PS_DATA_F32 | PS_META_REPLACE, "sky model minimum value", stats->min);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_NX",   PS_DATA_S32 | PS_META_REPLACE, "sky model size (x)",      nx);
+    psMetadataAdd (recipe, PS_LIST_TAIL, "SKY_NY",   PS_DATA_S32 | PS_META_REPLACE, "sky model size (y)",      ny);
+    psFree (stats);
+
+    // select background pixels, from output background file, or create
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKGND");
+    if (file) {
+	// we are using PSPHOT.BACKMDL as an I/O file: select readout or create
+	background = pmFPAviewThisReadout (view, file->fpa);
+	if (background == NULL) {
+	    // readout does not yet exist: create from input
+	    pmFPAfileCopyStructureView (file->fpa, inFPA, 1, 1, view);
+	    background = pmFPAviewThisReadout (view, file->fpa);
+	    if (Nx != background->image->numCols) psAbort ("psphot", "inconsistent size");
+	    if (Ny != background->image->numRows) psAbort ("psphot", "inconsistent size");
+	}
+    } else {
+        background = pmFPAfileDefineInternal (config->files, "PSPHOT.BACKGND", Nx, Ny, PS_TYPE_F32);
+    }
+    psF32 **backData = background->image->data.F32;
+
+    // linear interpolation to full-scale
+    psImageUnbin (background->image, model->image, DX, DY, dx, dy);
+    psLogMsg ("psphot", 3, "build resampled image: %f sec\n", psTimerMark ("psphot"));
+
+    // back-sub image pixels, from output background file (don't create if not requested)
+    file = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKSUB");
+    if (file) {
+	// we are using PSPHOT.BACKMDL as an I/O file: select readout or create
+	backSub = pmFPAviewThisReadout (view, file->fpa);
+	if (backSub == NULL) {
+	    // readout does not yet exist: create from input
+	    pmFPAfileCopyStructureView (file->fpa, inFPA, 1, 1, view);
+	    backSub = pmFPAviewThisReadout (view, file->fpa);
+	}
+    }
+
+# ifdef TESTSAVE
+    psphotSaveImage (NULL, image, "image.fits");
+    psphotSaveImage (NULL, background->image, "back.fits");
+    psphotSaveImage (NULL, mask, "mask.fits");
+    psphotSaveImage (NULL, model->image, "backmdl.fits");
+# endif
+
+    // subtract the background model (save in backSub, if requested)
+    for (int j = 0; j < image->numRows; j++) {
+        for (int i = 0; i < image->numCols; i++) {
+	    image->data.F32[j][i] -= backData[j][i];
+	    if (backSub) {
+		backSub->image->data.F32[j][i] = image->data.F32[j][i];
+	    }
+        }
+    }
+
+# ifdef TESTSAVE
+    psphotSaveImage (NULL, image, "backsub.fits");
+# endif
+
+    psLogMsg ("psphot", 3, "subtracted background model: %f sec\n", psTimerMark ("psphot"));
+    psFree(rng);
+
+    // the pmReadout selected in this function are all view on entries in config->files
+    return true;
+}
Index: /tags/RHL_v1_1/psphot/src/psphotMagnitudes.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotMagnitudes.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotMagnitudes.c	(revision 9533)
@@ -0,0 +1,24 @@
+# include "psphot.h"
+
+bool psphotMagnitudes (psArray *sources, psMetadata *recipe, pmPSF *psf) {
+
+    bool status = false;
+    int Nap = 0;
+
+    psTimerStart ("psphot");
+
+    pmSourceMagnitudesInit (recipe);
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = (pmSource *) sources->data[i];
+	status = pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_APCORR | PM_SOURCE_PHOT_WEIGHT);
+	// status = pmSourceMagnitudes (source, psf, PM_SOURCE_PHOT_GROWTH | PM_SOURCE_PHOT_APCORR);
+	if (status) Nap ++;
+    }	
+
+    psLogMsg ("psphot.magnitudes", 4, "measure magnitudes : %f sec for %ld objects (%d with apertures)\n", psTimerMark ("psphot"), sources->n, Nap);
+    return true;
+}
+
+// XXX add in a measurement of the bright and faint completeness values
+// XXX push these into the recipe
Index: /tags/RHL_v1_1/psphot/src/psphotMaskReadout.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotMaskReadout.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotMaskReadout.c	(revision 9533)
@@ -0,0 +1,26 @@
+# include "psphot.h"
+
+// 2006.02.04 : no leaks
+bool psphotMaskReadout (pmReadout *readout, psMetadata *recipe) {
+    
+    bool status;
+
+    // mask the excluded outer pixels
+    // these coordinates refer to the parent image
+    // these bounds will saturate on the subimage
+    // negative upper bounds will subtract from the *subimage*
+    float XMIN  = psMetadataLookupF32 (&status, recipe, "XMIN");
+    float XMAX  = psMetadataLookupF32 (&status, recipe, "XMAX");
+    float YMIN  = psMetadataLookupF32 (&status, recipe, "YMIN");
+    float YMAX  = psMetadataLookupF32 (&status, recipe, "YMAX");
+    psRegion valid = psRegionSet (XMIN, XMAX, YMIN, YMAX);
+
+    // psRegionForImage creates a region for the subimage, not the parent
+    // XXX I propose that psRegionForImage treat the region as referring to the parent...
+    psRegion keep = psRegionForImage (readout->image, valid);
+
+    // psImageKeepRegion assumes the region refers to the parent coordinates
+    psImageKeepRegion (readout->mask, keep, "OR", PM_MASK_BAD);
+
+    return true;
+}
Index: /tags/RHL_v1_1/psphot/src/psphotModelGroupInit.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotModelGroupInit.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotModelGroupInit.c	(revision 9533)
@@ -0,0 +1,23 @@
+# include "psphot.h"
+
+// Add locally-defined models here.  As these mature, they can be moved to 
+// psModule/src/objects/models
+
+# include "models/pmModel_TAUSS.c"
+
+static pmModelGroup userModels[] = {
+    {"PS_MODEL_TAUSS", 9, pmModelFunc_TAUSS,  pmModelFlux_TAUSS,  pmModelRadius_TAUSS,  pmModelLimits_TAUSS,  pmModelGuess_TAUSS, pmModelFromPSF_TAUSS, pmModelFitStatus_TAUSS},
+};
+
+void psphotModelGroupInit (void) 
+{ 
+
+    // if pmModelGroupInit returns false, we have already init-ed
+    if (!pmModelGroupInit ()) return;
+
+    int Nmodels = sizeof (userModels) / sizeof (pmModelGroup);
+    for (int i = 0; i < Nmodels; i++) {
+	pmModelGroupAdd (&userModels[i]);
+    }
+    return;
+}
Index: /tags/RHL_v1_1/psphot/src/psphotModelTest.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotModelTest.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotModelTest.c	(revision 9533)
@@ -0,0 +1,191 @@
+# include "psphot.h"
+static char DEFAULT_MODE[] = "EXT";
+
+bool psphotModelTest (pmReadout *readout, psMetadata *recipe) {
+
+    bool status;
+    int modelType;
+    unsigned int Nfail;
+    float obsMag, fitMag, value;
+    char name[64];
+    pmPSF *psf = NULL;
+    pmSourceFitMode fitMode;
+
+    psMetadataItem *item  = NULL;
+
+    // use poissonian errors or local-sky errors
+    bool POISSON_ERRORS = psMetadataLookupBool (&status, recipe, "POISSON_ERRORS");
+    if (!status) POISSON_ERRORS = true;
+    pmSourceFitModelInit (15, 0.1, POISSON_ERRORS);
+
+    // run model fitting tests on a single source
+    if (!psMetadataLookupBool (&status, recipe, "TEST_FIT")) return false;
+
+    // what fitting mode to use?
+    char *fitModeWord = psMetadataLookupStr (&status, recipe, "TEST_FIT_MODE");
+    if (!status) {
+	fitModeWord = DEFAULT_MODE;
+    }
+    fitMode = PM_SOURCE_FIT_EXT;
+    if (!strcasecmp (fitModeWord, "PSF")) fitMode = PM_SOURCE_FIT_PSF;
+
+    // in fitMode, psf sets the model type
+    if (fitMode == PM_SOURCE_FIT_PSF) {
+	char *psfFile = psMetadataLookupStr (&status, recipe, "PSF_INPUT_FILE");
+	if (!status) psAbort ("psphotModelTest", "PSF_INPUT_FILE not supplied");
+	psMetadata *psfData = psMetadataConfigRead(NULL, &Nfail, psfFile, FALSE);
+	psf = pmPSFfromMD (psfData);
+	modelType = psf->type;
+    } else {
+	// find the model: supplied by user or first in the PSF_MODEL list
+	char *modelName  = psMetadataLookupStr (&status, recipe, "TEST_FIT_MODEL");
+	if (modelName == NULL) {
+	    // get the list pointers for the PSF_MODEL entries
+
+	    psList *list = NULL;
+	    psMetadataItem *mdi = psMetadataLookup (recipe, "PSF_MODEL");
+	    if (mdi == NULL) psAbort ("psphotModelTest", "missing PSF_MODEL selection");
+	    if (mdi->type == PS_DATA_STRING) {
+		list = psListAlloc(NULL);
+		psListAdd (list, PS_LIST_HEAD, mdi);
+	    } else {
+		if (mdi->type != PS_DATA_METADATA_MULTI) psAbort ("psphotChoosePSF", "missing PSF_MODEL selection");
+		list = psMemIncrRefCounter(mdi->data.list);
+	    }
+
+	    // take the first list element
+	    item = psListGet (list, PS_LIST_HEAD);
+	    modelName = item->data.V;
+	}
+	modelType = pmModelSetType (modelName);
+	if (modelType < 0) psAbort ("fitsource", "unknown model %s", modelName);
+    }
+
+    // find the fitting parameters (try test values first)
+    float INNER = psMetadataLookupF32 (&status, recipe, "TEST_FIT_INNER_RADIUS");
+    if (!status) {
+	INNER = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
+    }
+
+    float OUTER = psMetadataLookupF32 (&status, recipe, "TEST_FIT_OUTER_RADIUS");
+    if (!status) {
+	OUTER = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    }
+
+    float RADIUS = psMetadataLookupF32 (&status, recipe, "TEST_FIT_RADIUS");
+    if (!status) {
+	RADIUS = psMetadataLookupF32 (&status, recipe, "PSF_FIT_RADIUS");
+    }
+
+    float mRADIUS = psMetadataLookupF32 (&status, recipe, "TEST_MOMENTS_RADIUS");
+    if (!status) {
+	mRADIUS = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    }
+
+    // define the source of interest
+    float xObj     = psMetadataLookupF32 (&status, recipe, "TEST_FIT_X");
+    float yObj     = psMetadataLookupF32 (&status, recipe, "TEST_FIT_Y");
+
+    // construct the source structures
+    pmSource *source = pmSourceAlloc();
+    source->peak = pmPeakAlloc (xObj, yObj, 0, 0);
+    pmSourceDefinePixels (source, readout, xObj, yObj, OUTER);
+
+    // find the local sky
+    status = pmSourceLocalSky (source, PS_STAT_SAMPLE_MEDIAN, INNER);
+    if (!status) psAbort ("fitsource", "pmSourceLocalSky error");
+
+    // get the source moments
+    status = pmSourceMoments (source, mRADIUS);
+    if (!status) psAbort ("fitsource", "pmSourceLocalSky error");
+    source->peak->counts = source->moments->Peak;
+
+    fprintf (stderr, "sum: %f @ (%f, %f)\n", source->moments->Sum, source->moments->x, source->moments->y);
+
+    psEllipseMoments moments;
+    moments.x2 = source->moments->Sx;
+    moments.y2 = source->moments->Sy;
+    moments.xy = source->moments->Sxy;
+    psEllipseAxes axes = psEllipseMomentsToAxes (moments);
+
+    fprintf (stderr, "axes: %f @ (%f, %f)\n", axes.theta*180/M_PI, axes.major, axes.minor);
+
+    // get the initial model parameter guess
+    pmModel *model = pmSourceModelGuess (source, modelType);
+    // if any parameters are defined, use those values
+    int nParams = pmModelParameterCount (modelType);
+    psF32 *params = model->params->data.F32;
+    for (int i = 0; i < nParams; i++) {
+	if (i == 2) {
+	    params[i] = xObj;
+	    continue;
+	}
+	if (i == 3) {
+	    params[i] = yObj;
+	    continue;
+	}
+	sprintf (name, "TEST_FIT_PAR%d", i);
+	value = psMetadataLookupF32 (&status, recipe, name);
+	if (status) {
+	    params[i] = value;
+	}
+    }
+
+    float area = params[4]*params[5];
+    fprintf (stderr, "peak: %f @ (%f, %f)\n", source->moments->Sum*area, (double)source->peak->x, (double)source->peak->y);
+
+    if (fitMode == PM_SOURCE_FIT_PSF) {
+	pmModel *modelPSF = pmModelFromPSF (model, psf);
+	psFree (model);
+	model = modelPSF;
+	params = model->params->data.F32;
+    }
+
+    // list model input shape
+    psEllipseShape shape;
+    shape.sx  = 1.4 / model->params->data.F32[4];
+    shape.sy  = 1.4 / model->params->data.F32[5];
+    shape.sxy = model->params->data.F32[6];
+    axes = psEllipseShapeToAxes (shape);
+
+    fprintf (stderr, "guess: %f @ (%f, %f)\n", axes.theta*180/M_PI, axes.major, axes.minor);
+
+
+    fprintf (stderr, "input parameters: \n");
+    for (int i = 0; i < nParams; i++) {
+	fprintf (stderr, "%d : %f\n", i, params[i]);
+    }
+
+    // define the pixels used for the fit
+    psImageKeepCircle (source->mask, xObj, yObj, RADIUS, "OR", PM_MASK_MARK);
+
+    char *fitset = psMetadataLookupStr (&status, recipe, "TEST_FIT_SET");
+    if (status) {
+	status = psphotFitSet (source, model, fitset, fitMode);
+	exit (0);
+    }
+
+    status = pmSourceFitModel (source, model, fitMode);
+
+    // measure the source mags
+    pmSourcePhotometryModel (&fitMag, model);
+    pmSourcePhotometryAper  (&obsMag, model, source->pixels, source->mask);
+    fprintf (stderr, "ap: %f, fit: %f, apmifit: %f\n", obsMag, fitMag, obsMag - fitMag);
+
+    // write out positive object
+    psphotSaveImage (NULL, source->pixels, "object.fits");
+
+    // subtract object, leave local sky
+    pmModelSub (source->pixels, source->mask, model, false, false);
+    
+    fprintf (stderr, "output parameters: \n");
+    for (int i = 0; i < nParams; i++) {
+	fprintf (stderr, "%d : %f\n", i, params[i]);
+    }
+
+    // write out 
+    psphotSaveImage (NULL, source->pixels, "resid.fits");
+    psphotSaveImage (NULL, source->mask, "mask.fits");
+
+    exit (0);
+}
Index: /tags/RHL_v1_1/psphot/src/psphotOutput.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotOutput.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotOutput.c	(revision 9533)
@@ -0,0 +1,155 @@
+# include "psphot.h"
+
+int psphotSaveImage (psMetadata *header, psImage *image, char *filename) {
+
+    psFits *fits = psFitsOpen (filename, "w");
+    psFitsWriteImage (fits, NULL, image, 0, NULL);
+    psFitsClose (fits);
+    return (TRUE);
+}
+
+bool psphotDumpMoments (psMetadata *recipe, psArray *sources) {
+
+    bool status;
+
+    // optional dump of all rough source data
+    char *output = psMetadataLookupStr (&status, recipe, "MOMENTS_OUTPUT_FILE");
+    if (!status) return false;
+    if (output == NULL) return false;
+    if (output[0] == 0) return false;
+
+    pmMomentsWriteText (sources, output);
+    return true;
+}
+
+bool psphotDumpSource (pmSource *source, char *name) {
+
+    FILE *f = fopen (name, "w");
+    if (f == NULL) psAbort ("psphot", "can't open file");
+
+    for (int i = 0; i < source->pixels->numRows; i++) {
+        for (int j = 0; j < source->pixels->numCols; j++) {
+            // skip masked points
+            if (source->mask->data.U8[i][j]) {
+                continue;
+            }
+            // skip zero-weight points
+            if (source->weight->data.F32[i][j] == 0) {
+                continue;
+            }
+
+	    fprintf (f, "%d %d %f %f %d\n", 
+		     (j + source->pixels->col0),
+		     (i + source->pixels->row0),
+		     source->pixels->data.F32[i][j],
+		     1.0 / source->weight->data.F32[i][j],
+		     source->mask->data.U8[i][j]);
+        }
+    }
+    fclose (f);
+    return true;
+}
+
+// these values are saved in an output header stub - they are added to either the
+// PHU header (CMP) or the MEF table header (CMF)
+psMetadata *psphotDefineHeader (psMetadata *recipe) {
+
+    psMetadata *header = psMetadataAlloc ();
+
+    // write necessary information to output header
+    psMetadataItemTransfer (header, recipe, "ZERO_PT");
+    psMetadataItemTransfer (header, recipe, "PHOTCODE");
+
+    psMetadataItemTransfer (header, recipe, "APMIFIT");
+    psMetadataItemTransfer (header, recipe, "DAPMIFIT");
+    psMetadataItemTransfer (header, recipe, "NAPMIFIT");
+    psMetadataItemTransfer (header, recipe, "SKYBIAS");
+    psMetadataItemTransfer (header, recipe, "SKYSAT");
+    
+    // PSF model parameters (shape values for image center)
+    psMetadataItemTransfer (header, recipe, "NPSFSTAR");
+    psMetadataItemTransfer (header, recipe, "APLOSS");
+    psMetadataItemTransfer (header, recipe, "FWHM_X");
+    psMetadataItemTransfer (header, recipe, "FWHM_Y");
+    psMetadataItemTransfer (header, recipe, "ANGLE");
+
+    // XXX these need to be defined from elsewhere
+    psMetadataAdd (header, PS_LIST_TAIL, "FSATUR",   PS_DATA_F32 | PS_META_REPLACE, "SATURATION MAG",      0.0);
+    psMetadataAdd (header, PS_LIST_TAIL, "FLIMIT",   PS_DATA_F32 | PS_META_REPLACE, "COMPLETENESS MAG",    0.0);
+
+    // sky background model statistics
+    psMetadataItemTransfer (header, recipe, "SKY_MEAN");
+    psMetadataItemTransfer (header, recipe, "SKY_STDV");
+    psMetadataItemTransfer (header, recipe, "SKY_MIN");
+    psMetadataItemTransfer (header, recipe, "SKY_MAX");
+    psMetadataItemTransfer (header, recipe, "SKY_NX");
+    psMetadataItemTransfer (header, recipe, "SKY_NY");
+
+    return header;
+}
+
+# if (0)
+// output functions: we have several fixed modes (sx, obj, cmp)
+void psphotOutput (pmReadout *readout, psMetadata *arguments) {
+
+    bool status;
+    char *outputFile = NULL;
+
+    psMetadata *header = pmReadoutGetHeader (readout);
+
+    pmPSF *psf = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.PSF");
+    // sample PSF images??
+    if (psfSample != NULL) psphotSamplePSFs (psf, readout->image, psfSample);
+    if (psfSample != NULL) psphotSamplePSFs (psf, readout->image, psfSample);
+
+    if (psfFile != NULL) {
+	psMetadata *psfData = pmPSFtoMD (NULL, psf);
+	psMetadataConfigWrite (psfData, psfFile);
+	psFree (psfData);
+    }
+}
+
+psImage *pmModelPSFatXY (psImage *image, pmModel *modelEXT, pmPSF *psf, int x, int y, int dx, int dy) {
+
+    psRegion region = {x - dx, x + dx, y - dy, y + dy};
+    psImage *view = psImageSubset (image, region);
+    psImage *sample = psImageCopy (NULL, view, PS_TYPE_F32);
+    psImageInit (sample, 0);
+    modelEXT->params->data.F32[2] = x;
+    modelEXT->params->data.F32[3] = y;
+    pmModel *modelPSF = pmModelFromPSF (modelEXT, psf);
+    pmModelAdd (sample, NULL, modelPSF, false, false);
+    psFree (modelPSF);
+    return (sample);
+}
+
+bool psphotSamplePSFs (pmPSF *psf, psImage *image, char *output) {
+
+    // make sample PSFs for 4 corners and the center
+    psImage *sample;
+
+    // optional dump of all rough source data
+    if (output[0] == 0) return false;
+
+    pmModel *modelEXT = pmModelAlloc (psf->type);
+    modelEXT->params->data.F32[0] = 0;
+    modelEXT->params->data.F32[1] = 1;
+
+    psFits *fits = psFitsOpen (output, "w");
+
+    // the centers are in parent coordinates; they do not need to correspond to valid pixels...
+    sample = pmModelPSFatXY (image, modelEXT, psf, 25, 25, 25, 25);
+    psFitsWriteImage (fits, NULL, sample, 0);
+    sample = pmModelPSFatXY (image, modelEXT, psf, image->numCols - 25, image->numRows - 25, 25, 25);
+    psFitsWriteImage (fits, NULL, sample, 0);
+    sample = pmModelPSFatXY (image, modelEXT, psf, image->numCols - 25, 25, 25, 25);
+    psFitsWriteImage (fits, NULL, sample, 0);
+    sample = pmModelPSFatXY (image, modelEXT, psf, 25, image->numRows - 25, 25, 25);
+    psFitsWriteImage (fits, NULL, sample, 0);
+    sample = pmModelPSFatXY (image, modelEXT, psf, image->numCols / 2, image->numRows / 2, 25, 25);
+    psFitsWriteImage (fits, NULL, sample, 0);
+
+    psFitsClose (fits);
+    return (TRUE);
+}
+# endif
Index: /tags/RHL_v1_1/psphot/src/psphotParseCamera.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotParseCamera.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotParseCamera.c	(revision 9533)
@@ -0,0 +1,90 @@
+# include "psphot.h"
+
+// define the needed / desired I/O files
+bool psphotParseCamera (pmConfig *config) {
+
+    bool status = false;
+
+    // the input image defines the camera
+    pmFPAfile *input = pmFPAfileDefineFromArgs (&status, config, "PSPHOT.INPUT", "INPUT");
+    if (!status) {
+	psError(PSPHOT_ERR_CONFIG, false, "Failed to build FPA from PSPHOT.INPUT");
+	return status;
+    }
+
+    // select recipe options supplied on command line
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes,   "PSPHOT");
+    psMetadata *options = psMetadataLookupPtr (&status, config->arguments, "PSPHOT.OPTIONS");
+    psMetadataCopy (recipe, options);
+
+    // set default recipe values here
+    psMetadataAddStr (recipe, PS_LIST_TAIL, "FITMODE",         PS_META_NO_REPLACE, "default fitting mode", "NONE");
+    psMetadataAddStr (recipe, PS_LIST_TAIL, "PHOTCODE",        PS_META_NO_REPLACE, "default photcode",     "NONE");
+    psMetadataAddStr (recipe, PS_LIST_TAIL, "BREAK_POINT",     PS_META_NO_REPLACE, "default break point",  "NONE");
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "ZERO_PT",         PS_META_NO_REPLACE, "default zero point",    25.00);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "BACKGROUND.XBIN", PS_META_NO_REPLACE, "default binning",          64);
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "BACKGROUND.YBIN", PS_META_NO_REPLACE, "default binning",          64);
+
+    // these calls bind the I/O handle to the specified fpa
+    if (!psMetadataLookup(recipe, "SAVE.OUTPUT") ||
+	psMetadataLookupBool(NULL, recipe, "SAVE.OUTPUT")) {
+	if (!pmFPAfileDefineOutput (config, input->fpa, "PSPHOT.OUTPUT")) {
+	    psError(PS_ERR_IO, false, "Cannot find a rule for PSPHOT.OUTPUT");
+	    return false;
+	}
+    }
+
+    // optionally save the residual image 
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.RESID")) {
+	pmFPAfileDefineOutput (config, input->fpa, "PSPHOT.RESID");
+    }
+
+    int DX = psMetadataLookupS32 (&status, recipe, "BACKGROUND.XBIN");
+    int DY = psMetadataLookupS32 (&status, recipe, "BACKGROUND.YBIN");
+
+    // these calls construct a new fpa for the I/O handle 
+
+    // optionally save the background model (small FITS image)
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKMDL")) {
+	pmFPAfileDefineFromFPA (config, input->fpa, DX, DY, "PSPHOT.BACKMDL");
+    }
+    // optionally save the full background image
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKGND")) {
+	pmFPAfileDefineFromFPA (config, input->fpa,  1,  1, "PSPHOT.BACKGND");
+    }
+    // optionally save the background-subtracted image
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.BACKSUB")) {
+	pmFPAfileDefineFromFPA (config, input->fpa,  1,  1, "PSPHOT.BACKSUB");
+    }
+    // optionally save the PSF Model
+    if (psMetadataLookupBool(NULL, recipe, "SAVE.PSF")) {
+	pmFPAfileDefineOutput (config, input->fpa, "PSPHOT.PSF.SAVE");
+    }
+    // optionally load the PSF Model
+    if (psMetadataLookupBool(NULL, recipe, "LOAD.PSF")) {
+	pmFPAfileDefineInput (config, input->fpa, "PSPHOT.PSF.LOAD");
+    }
+
+    // pmFPAfileConstruct (config->files, format, config->camera, "PSPHOT.PSF_SAMPLE");
+
+    // Chip selection: turn on only the chips specified (pass status to suppress missing-key log msg)
+    char *chipLine = psMetadataLookupStr(&status, config->arguments, "CHIP_SELECTIONS");
+    psArray *chips = psStringSplitArray (chipLine, ",", false);
+    if (chips->n > 0) {
+	pmFPASelectChip (input->fpa, -1, true); // deselect all chips
+	for (int i = 0; i < chips->n; i++) {
+	    int chipNum = atoi(chips->data[i]);
+	    if (! pmFPASelectChip(input->fpa, chipNum, false)) {
+		psError(PS_ERR_IO, false, "Chip number %d doesn't exist in camera.\n", chipNum);
+		return false;
+	    }
+        }
+    }
+    psFree (chips);
+
+    psTrace(__func__, 1, "Done with psphotParseCamera...\n");
+
+    psErrorClear();			// some metadata lookup may have failed
+
+    return true;
+}
Index: /tags/RHL_v1_1/psphot/src/psphotRadiusChecks.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotRadiusChecks.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotRadiusChecks.c	(revision 9533)
@@ -0,0 +1,82 @@
+# include "psphot.h"
+# define RADIUS_TYPE float
+
+static float PSF_FIT_NSIGMA;
+static float PSF_FIT_PADDING;
+static pmModelRadius modelRadiusPSF;
+
+bool psphotInitRadiusPSF (psMetadata *recipe, pmModelType type) {
+
+    bool status;
+
+    PSF_FIT_NSIGMA   = psMetadataLookupF32 (&status, recipe, "PSF_FIT_NSIGMA");
+    PSF_FIT_PADDING  = psMetadataLookupF32 (&status, recipe, "PSF_FIT_PADDING");
+
+    // this function specifies the radius at this the model hits the given flux
+    modelRadiusPSF       = pmModelRadius_GetFunction (type);
+    return true;
+}
+
+bool psphotCheckRadiusPSF (pmReadout *readout, pmSource *source, pmModel *model) {
+
+    pmMoments *moments = source->moments;
+    if (moments == NULL) return false;
+
+    // set the fit radius based on the object flux limit and the model
+    model->radiusTMP = (RADIUS_TYPE) (modelRadiusPSF (model->params, PSF_FIT_NSIGMA*moments->dSky) + PSF_FIT_PADDING);
+    if (isnan(model->radiusTMP)) psAbort ("apply_psf_model", "error in radius");
+	
+    if (source->mode & PM_SOURCE_MODE_SATSTAR) {
+	model->radiusTMP *= 2;
+    }
+
+    bool status = pmSourceRedefinePixels (source, readout, model->params->data.F32[2], model->params->data.F32[3], model->radiusTMP);
+    return status;
+}
+
+bool psphotCheckRadiusPSFBlend (pmReadout *readout, pmSource *source, pmModel *model, float dR) {
+
+    pmMoments *moments = source->moments;
+    if (moments == NULL) return false;
+
+    // set the fit radius based on the object flux limit and the model
+    model->radiusTMP = (RADIUS_TYPE) (modelRadiusPSF (model->params, PSF_FIT_NSIGMA*moments->dSky) + dR + PSF_FIT_PADDING);
+    if (isnan(model->radiusTMP)) psAbort ("apply_psf_model", "error in radius");
+	
+    if (source->mode &  PM_SOURCE_MODE_SATSTAR) {
+	model->radiusTMP *= 2;
+    }
+
+    bool status = pmSourceRedefinePixels (source, readout, model->params->data.F32[2], model->params->data.F32[3], model->radiusTMP);
+    return status;
+}
+
+static float EXT_FIT_NSIGMA;
+static float EXT_FIT_PADDING;
+static pmModelRadius modelRadiusEXT;
+
+bool psphotInitRadiusEXT (psMetadata *recipe, pmModelType type) {
+
+    bool status;
+
+    EXT_FIT_NSIGMA   = psMetadataLookupF32 (&status, recipe, "EXT_FIT_NSIGMA");
+    EXT_FIT_PADDING  = psMetadataLookupF32 (&status, recipe, "EXT_FIT_PADDING");
+
+    // this function specifies the radius at this the model hits the given flux
+    modelRadiusEXT       = pmModelRadius_GetFunction (type);
+    return true;
+}
+
+bool psphotCheckRadiusEXT (pmReadout *readout, pmSource *source, pmModel *model) {
+
+    pmMoments *moments = source->moments;
+    if (moments == NULL) return false;
+
+    // set the fit radius based on the object flux limit and the model
+    model->radiusTMP = (RADIUS_TYPE) (modelRadiusEXT (model->params, EXT_FIT_NSIGMA*moments->dSky) + EXT_FIT_PADDING);
+    if (isnan(model->radiusTMP)) psAbort (__func__, "error in radius");
+
+    // redefine the pixels if needed
+    bool status = pmSourceRedefinePixels (source, readout, model->params->data.F32[2], model->params->data.F32[3], model->radiusTMP);
+    return status;
+}
Index: /tags/RHL_v1_1/psphot/src/psphotReadout.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotReadout.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotReadout.c	(revision 9533)
@@ -0,0 +1,146 @@
+# include "psphot.h"
+
+bool psphotReadout (pmConfig *config, pmFPAview *view) {
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, "PSPHOT");
+
+    // find the currently selected readout
+    pmReadout  *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
+
+    // optional break-point for processing
+    char *breakPt = psMetadataLookupStr (NULL, recipe, "BREAK_POINT");
+
+    // XXX does this need to invoke I/O?
+    // XXX move this input the psphotImageLoop level?
+    if (!pmReadoutGenerateMaskWeight (readout)) {
+        return false;
+    }
+
+    // I have a valid mask, now mask in the analysis region of interest
+    psphotMaskReadout (readout, recipe);
+
+    // run a single-model test if desired
+    psphotModelTest (readout, recipe);
+
+    // psphotSaveImage (NULL, readout->image,  "image.fits");
+    // psphotSaveImage (NULL, readout->mask,   "mask.fits");
+    // psphotSaveImage (NULL, readout->weight, "weight.fits");
+
+    // generate a background model (median, smoothed image)
+    psphotImageMedian (config, view);
+
+    // find the peaks in the image
+    psArray *peaks = psphotFindPeaks (readout, recipe);
+
+    // construct sources and measure basic stats
+    // limit moments analysis by S/N?
+    psArray *sources = psphotSourceStats (readout, recipe, peaks);
+    psFree (peaks);
+
+    if (!strcasecmp (breakPt, "PEAKS")) {
+	if (!psphotReadoutCleanup (config, readout, recipe, NULL, sources)) return false;
+	return true;
+    }
+
+    // mark blended peaks PS_SOURCE_BLEND
+    psphotBasicDeblend (sources, recipe);
+
+    // classify sources based on moments, brightness
+    // faint sources not classified?
+    if (!psphotRoughClass (sources, recipe)) {
+        psFree (sources);
+        psLogMsg ("psphot", 3, "failed to find a valid PSF clump for image");
+        pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL");
+        pmFPAfileDropInternal (config->files, "PSPHOT.BACKGND");
+        return false;
+    }
+    if (!strcasecmp (breakPt, "MOMENTS")) {
+	if (!psphotReadoutCleanup (config, readout, recipe, NULL, sources)) return false;
+	return true;
+    }
+
+    // use bright stellar objects to measure PSF
+    pmPSF *psf = psphotChoosePSF (readout, sources, recipe);
+    if (!strcasecmp (breakPt, "PSFMODEL")) {
+	if (!psphotReadoutCleanup (config, readout, recipe, psf, sources)) return false;
+	return true;
+    }
+
+    // linear PSF fit to peaks
+    psphotEnsemblePSF (readout, sources, recipe, psf, FALSE);
+    if (!strcasecmp (breakPt, "ENSEMBLE")) {
+	if (!psphotReadoutCleanup (config, readout, recipe, psf, sources)) return false;
+	return true;
+    }
+
+    // non-linear PSF and EXT fit to brighter sources
+    psphotBlendFit (readout, sources, recipe, psf);
+
+    // XXX this was an attempt to measure bias due to the weighting.
+    // psphotWeightBias (readout, sources, recipe, psf);
+
+    // replace fitted sources
+    psphotReplaceUnfit (sources);
+
+    // XXX find remaining peaks after first source subtraction pass
+    // psphotFindPeaks ();
+
+    // linear PSF fit to remaining peaks
+    // psphotEnsemblePSF (readout, sources, recipe, psf, TRUE);
+
+    // measure aperture photometry corrections
+    if (!psphotApResid (readout, sources, recipe, psf)) {
+	psTrace ("psphotReadout", 4, "failure on psphotApResid"); 
+        psError(PSPHOT_ERR_PHOTOM, false, "Measure aperture photometry corrections");
+        return false;
+    }
+
+    // calculate source magnitudes
+    psphotMagnitudes (sources, recipe, psf);
+
+    // replace background in residual image
+    psphotSkyReplace (config, view);
+
+    // drop the references to the image pixels held by each source
+    psphotSourceFreePixels (sources);
+    // psphotSaveImage (NULL, readout->image, "resid.fits");
+
+    // create the exported-metadata and free local data
+    if (!psphotReadoutCleanup (config, readout, recipe, psf, sources)) return false;
+    return true;
+}
+
+bool psphotReadoutCleanup (pmConfig *config, pmReadout *readout, psMetadata *recipe, pmPSF *psf, psArray *sources) {
+
+    // use the psf-model to measure FWHM stats
+    if (psf) {
+	if (!psphotPSFstats (readout, recipe, psf)) {
+	    psError(PS_ERR_IO, true, "Failed to measure PSF shape parameters");
+	    return false;
+	}
+    } else if (sources) {
+	if (!psphotMomentsStats (readout, recipe, sources)) {
+	    psError(PS_ERR_IO, true, "Failed to measure Moment shape parameters");
+	    return false;
+	}
+    }
+
+    // create an output header with stats results
+    psMetadata *header = psphotDefineHeader (recipe);
+
+    // save the results of the analysis
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES", PS_DATA_ARRAY,    "psphot sources", sources);
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.HEADER",  PS_DATA_METADATA, "header stats", header);
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.PSF",     PS_DATA_UNKNOWN,  "psphot psf", psf);
+
+    // remove internal pmFPAfiles, if created
+    pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL");
+    pmFPAfileDropInternal (config->files, "PSPHOT.BACKGND");
+
+    psFree (psf);
+    psFree (header);
+    psFree (sources);
+
+    return true;
+}
Index: /tags/RHL_v1_1/psphot/src/psphotReplaceUnfit.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotReplaceUnfit.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotReplaceUnfit.c	(revision 9533)
@@ -0,0 +1,24 @@
+# include "psphot.h"
+
+bool psphotReplaceUnfit (psArray *sources) { 
+
+    pmSource *source;
+
+    psTimerStart ("psphot");
+
+    for (int i = 0; i < sources->n; i++) {
+      source = sources->data[i];
+
+	if (!(source->mode & PM_SOURCE_MODE_TEMPSUB)) continue;
+	if (source->modelPSF == NULL) continue;
+
+	psTrace (__func__, 3, "replacing object at %f,%f\n", 
+		 source->modelPSF->params->data.F32[2], source->modelPSF->params->data.F32[3]);
+
+	pmModelAdd (source->pixels, source->mask, source->modelPSF, false, false);
+	source->mode &= ~PM_SOURCE_MODE_SUBTRACTED;
+	source->mode &= ~PM_SOURCE_MODE_TEMPSUB;
+    }
+    psLogMsg ("psphot.replace", 3, "replace unfitted models: %f sec (%ld objects)\n", psTimerMark ("psphot"), sources->n);
+    return true;
+}
Index: /tags/RHL_v1_1/psphot/src/psphotRoughClass.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotRoughClass.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotRoughClass.c	(revision 9533)
@@ -0,0 +1,25 @@
+# include "psphot.h"
+
+// 2006.02.02 : no leaks
+bool psphotRoughClass (psArray *sources, psMetadata *recipe) {
+
+    pmPSFClump   psfClump;
+
+    psfClump = pmSourcePSFClump (sources, recipe);
+    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);
+
+    // did we find a valid psfClump?
+    if (!psfClump.X || !psfClump.Y) {
+	psError(PSPHOT_ERR_PSF, true, "Failed to find a valid PSF clump");
+	return false;
+    }
+
+    // group into STAR, COSMIC, EXTENDED, SATURATED, etc.
+    pmSourceRoughClass (sources, recipe, psfClump);
+
+    // optional printout of source moments only
+    psphotDumpMoments (recipe, sources);
+
+    return true;
+}
Index: /tags/RHL_v1_1/psphot/src/psphotSkyReplace.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotSkyReplace.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotSkyReplace.c	(revision 9533)
@@ -0,0 +1,30 @@
+# include "psphot.h"
+
+// XXX make this an option?
+// in order to  successfully replace the sky, we must define a corresponding file...
+bool psphotSkyReplace (pmConfig *config, pmFPAview *view) {
+
+    // find the currently selected readout
+    pmReadout *readout = pmFPAfileThisReadout (config->files, view, "PSPHOT.INPUT");
+    if (readout == NULL) psAbort ("psphot", "input not defined");
+
+    // select background pixels, from output background file, or create
+    pmReadout *background = pmFPAfileThisReadout (config->files, view, "PSPHOT.BACKGND");
+    if (background == NULL) psAbort ("psphot", "background not defined");
+
+    // select the corresponding images
+    psF32 **image = readout->image->data.F32;
+    psU8  **mask  = readout->mask->data.U8;
+    psF32 **back  = background->image->data.F32;
+
+    // replace the background model
+    for (int j = 0; j < readout->image->numRows; j++) {
+	for (int i = 0; i < readout->image->numCols; i++) {
+	    if (!mask[j][i]) {
+		image[j][i] += back[j][i];
+	    }
+	}
+    }
+    return true;
+}
+
Index: /tags/RHL_v1_1/psphot/src/psphotSortBySN.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotSortBySN.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotSortBySN.c	(revision 9533)
@@ -0,0 +1,34 @@
+# include "psphot.h"
+
+// sort by SN (descending)
+int psphotSortBySN (const void **a, const void **b)
+{
+    pmSource *A = *(pmSource **)a;
+    pmSource *B = *(pmSource **)b;
+
+    psF32 fA = (A->moments == NULL) ? 0 : A->moments->SN;
+    psF32 fB = (B->moments == NULL) ? 0 : B->moments->SN;
+    if (isnan (fA)) fA = 0;
+    if (isnan (fB)) fB = 0;
+
+    psF32 diff = fA - fB;
+    if (diff > FLT_EPSILON) return (-1);
+    if (diff < FLT_EPSILON) return (+1);
+    return (0);
+}
+
+// sort by Y (ascending)
+int psphotSortByY (const void **a, const void **b)
+{
+    pmSource *A = *(pmSource **)a;
+    pmSource *B = *(pmSource **)b;
+
+    psF32 fA = (A->moments == NULL) ? 0 : A->moments->y;
+    psF32 fB = (B->moments == NULL) ? 0 : B->moments->y;
+
+    psF32 diff = fA - fB;
+    if (diff > FLT_EPSILON) return (+1);
+    if (diff < FLT_EPSILON) return (-1);
+    return (0);
+}
+
Index: /tags/RHL_v1_1/psphot/src/psphotSourceFits.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotSourceFits.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotSourceFits.c	(revision 9533)
@@ -0,0 +1,351 @@
+# include "psphot.h"
+
+// given a source with an existing modelPSF, attempt a full PSF fit, subtract if successful
+
+bool psphotFitBlend (pmReadout *readout, pmSource *source, pmPSF *psf) { 
+
+    float x, y, dR;
+
+    // if this source is not a possible blend, just fit as PSF
+    if ((source->blends == NULL) || (source->mode & PM_SOURCE_MODE_SATSTAR)) {
+	bool status = psphotFitPSF (readout, source, psf);
+	return status;
+    }
+    psTrace ("psphot.blend", 5, "trying blend...\n");
+
+    // save the PSF model from the Ensemble fit
+    pmModel *PSF = pmModelCopy (source->modelPSF);
+    if (isnan(PSF->params->data.F32[1])) psAbort ("psphot", "nan in blend fit primary");
+
+    x = PSF->params->data.F32[2];
+    y = PSF->params->data.F32[3];
+
+    psArray *modelSet = psArrayAlloc (source->blends->n + 1);
+    psArrayAdd (modelSet, 16, PSF);
+    
+    psArray *sourceSet = psArrayAlloc (source->blends->n + 1);
+    psArrayAdd (sourceSet, 16, source);
+
+    // we need to include all blends in the fit (unless primary is saturated?)
+    dR = 0;
+    for (int i = 0; i < source->blends->n; i++) {
+	pmSource *blend = source->blends->data[i];
+
+	// find the blend which is furthest from source
+	dR = PS_MAX (dR, hypot (blend->peak->x - x, blend->peak->y - 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[1] = blend->moments->Peak - blend->moments->Sky;
+	if (isnan(model->params->data.F32[1])) psAbort ("psphot", "nan in blend fit");
+	model->params->data.F32[2] = blend->peak->x;
+	model->params->data.F32[3] = blend->peak->y;
+
+	// add this blend to the list
+	psArrayAdd (modelSet, 16, model);
+	psArrayAdd (sourceSet, 16, blend);
+
+	// free to avoid double counting model 
+	psFree (model);
+    }
+
+    // extend source radius as needed
+    psphotCheckRadiusPSFBlend (readout, source, PSF, dR);
+
+    // fit PSF model (set/unset the pixel mask)
+    psImageKeepCircle (source->mask, x, y, PSF->radiusTMP, "OR", PM_MASK_MARK);
+    pmSourceFitSet (source, modelSet, PM_SOURCE_FIT_PSF);
+    psImageKeepCircle (source->mask, x, y, PSF->radiusTMP, "AND", NOT_U8(PM_MASK_MARK));
+
+    // correct model chisq for flux trend
+    double chiTrend = psPolynomial1DEval (psf->ChiTrend, PSF->params->data.F32[1]);
+    PSF->chisqNorm = PSF->chisq / chiTrend;
+
+    // evaluate the blend objects, subtract if good, free otherwise
+    for (int i = 1; i < modelSet->n; i++) {
+	pmSource *blend = sourceSet->data[i];
+	pmModel *model  = modelSet->data[i];
+
+	// correct model chisq for flux trend
+	chiTrend = psPolynomial1DEval (psf->ChiTrend, model->params->data.F32[1]);
+	model->chisqNorm = model->chisq / chiTrend;
+
+	// if this one failed, skip it
+	if (!psphotEvalPSF (blend, model)) {
+	    psTrace ("psphot.blend", 5, "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.blend", 5, "fitted blend as PSF\n");
+	pmModelSub (source->pixels, source->mask, model, false, false);
+	blend->mode |=  PM_SOURCE_MODE_SUBTRACTED;
+	blend->mode &= ~PM_SOURCE_MODE_TEMPSUB;
+    }
+    psFree (modelSet);
+    psFree (sourceSet);
+
+    // evaluate the primary object
+    if (!psphotEvalPSF (source, PSF)) {
+	psTrace ("psphot.blend", 5, "failed on blend 0 of %ld\n", modelSet->n);
+	psFree (PSF);
+	return false;
+    }
+
+    psTrace ("psphot.blend", 5, "fitted primary as PSF\n");
+    pmModelSub (source->pixels, source->mask, PSF, false, false);
+    psFree (source->modelPSF);
+    source->modelPSF = PSF;
+    source->mode |=  PM_SOURCE_MODE_SUBTRACTED;
+    source->mode &= ~PM_SOURCE_MODE_TEMPSUB;
+    return true;
+}
+
+bool psphotFitPSF (pmReadout *readout, pmSource *source, pmPSF *psf) { 
+
+    float x, y;
+    double chiTrend;
+
+    // save the PSF model from the Ensemble fit
+    pmModel *PSF = pmModelCopy (source->modelPSF);
+    if (isnan(PSF->params->data.F32[1])) psAbort ("psphot", "nan in psf fit");
+
+    // extend source radius as needed
+    psphotCheckRadiusPSF (readout, source, PSF);
+
+    x = PSF->params->data.F32[2];
+    y = PSF->params->data.F32[3];
+
+    // fit PSF model (set/unset the pixel mask)
+    psImageKeepCircle (source->mask, x, y, PSF->radiusTMP, "OR", PM_MASK_MARK);
+    pmSourceFitModel (source, PSF, PM_SOURCE_FIT_PSF);
+    psImageKeepCircle (source->mask, x, y, PSF->radiusTMP, "AND", NOT_U8(PM_MASK_MARK));
+    
+    // correct model chisq for flux trend
+    chiTrend = psPolynomial1DEval (psf->ChiTrend, PSF->params->data.F32[1]);
+    PSF->chisqNorm = PSF->chisq / chiTrend;
+
+    // does the PSF model succeed?
+    if (!psphotEvalPSF (source, PSF)) {
+	psFree (PSF);
+	return false;
+    }
+
+    psTrace ("psphot.blend", 5, "fitted as PSF\n");
+    pmModelSub (source->pixels, source->mask, PSF, false, false);
+
+    // free old model, save new model 
+    psFree (source->modelPSF);
+    source->modelPSF = PSF;
+
+    source->mode |=  PM_SOURCE_MODE_SUBTRACTED;
+    source->mode &= ~PM_SOURCE_MODE_TEMPSUB;
+
+    return true;
+}
+
+static float EXT_MIN_SN;
+static float EXT_MOMENTS_RAD;
+static pmModelType modelTypeEXT;
+
+bool psphotInitLimitsEXT (psMetadata *recipe) {
+
+    bool status;
+
+    // extended source model parameters
+    EXT_MIN_SN       = psMetadataLookupF32 (&status, recipe, "EXT_MIN_SN");
+    EXT_MOMENTS_RAD  = psMetadataLookupF32 (&status, recipe, "EXT_MOMENTS_RADIUS");
+
+    // extended source model descriptions
+    char *modelNameEXT = psMetadataLookupStr (&status, recipe, "EXT_MODEL");
+    modelTypeEXT = pmModelSetType (modelNameEXT);
+    psphotInitRadiusEXT (recipe, modelTypeEXT);
+
+    return true;
+}
+
+bool psphotFitBlob (pmReadout *readout, pmSource *source, psArray *sources, pmPSF *psf) { 
+
+    bool okEXT, okDBL;
+    float chiEXT, chiDBL;
+    double chiTrend;
+    pmModel *ONE = NULL;
+
+    // skip the source if we don't think it is extended
+    if (source->type == PM_SOURCE_TYPE_UNKNOWN) return false;
+    if (source->type == PM_SOURCE_TYPE_DEFECT) return false;
+    if (source->type == PM_SOURCE_TYPE_SATURATED) return false;
+    if (source->moments->SN < EXT_MIN_SN) return false;
+
+    // recalculate the source moments using the larger extended-source moments radius
+    if (!pmSourceMoments (source, EXT_MOMENTS_RAD)) return false;
+
+    psTrace ("psphot.blend", 5, "trying blob...\n");
+
+    // this temporary source is used as a place-holder by the psphotEval functions below
+    pmSource *tmpSrc = pmSourceAlloc ();
+
+    pmModel *EXT = psphotFitEXT (readout, source);
+    okEXT = psphotEvalEXT (tmpSrc, EXT);
+    chiEXT = EXT->chisq / EXT->nDOF;
+
+    psArray *DBL = psphotFitDBL (readout, source);
+    okDBL  = psphotEvalDBL (tmpSrc, DBL->data[0]);
+    okDBL &= psphotEvalDBL (tmpSrc, DBL->data[1]);
+    // XXX should I keep / save the flags set in the eval functions?
+
+    // correct first model chisqs for flux trend
+    ONE = DBL->data[0];
+    chiTrend = psPolynomial1DEval (psf->ChiTrend, ONE->params->data.F32[1]);
+    ONE->chisqNorm = ONE->chisq / chiTrend;
+
+    // save chisq for double-star/galaxy comparison
+    chiDBL = ONE->chisqNorm;
+
+    // correct second model chisqs for flux trend
+    ONE = DBL->data[1];
+    chiTrend = psPolynomial1DEval (psf->ChiTrend, ONE->params->data.F32[1]);
+    ONE->chisqNorm = ONE->chisq / chiTrend;
+
+    psFree (tmpSrc); 
+
+    if (okEXT && okDBL) {
+	psTrace ("psphot.blend", 5, "blob chisq: %f vs %f\n", chiEXT, chiDBL);
+	// XXX EAM : a bogus bias: need to examine this better 
+	if (3*chiEXT > chiDBL) goto keepDBL;
+	goto keepEXT;
+    }
+
+    if (okEXT && !okDBL) goto keepEXT;
+    if (!okEXT && okDBL) goto keepDBL;
+
+    // both models failed; reject them both
+    psFree (EXT);
+    psFree (DBL);
+    return false;
+
+keepEXT:
+    // sub EXT
+    psFree (DBL);
+    pmModelSub (source->pixels, source->mask, EXT, false, false);
+    psTrace ("psphot.blend", 5, "blob as EXT: %f %f\n", EXT->params->data.F32[2], EXT->params->data.F32[3]);
+
+    // save new model
+    source->modelEXT = EXT;
+    source->mode |=  PM_SOURCE_MODE_SUBTRACTED;
+    source->mode &= ~PM_SOURCE_MODE_TEMPSUB;
+    return true;
+
+keepDBL:
+    // sub DLB
+    psFree (EXT);
+    pmModelSub (source->pixels, source->mask, (pmModel *) DBL->data[0], false, false);
+    pmModelSub (source->pixels, source->mask, (pmModel *) DBL->data[1], false, false);
+    psTrace ("psphot.blend", 5, "blob as DBL: %f %f\n", ONE->params->data.F32[2], ONE->params->data.F32[3]);
+
+    // drop old model, save new second model...
+    psFree (source->modelPSF);
+    source->modelPSF = psMemIncrRefCounter (DBL->data[0]);
+    source->mode    |= PM_SOURCE_MODE_SUBTRACTED;
+    source->mode    |= PM_SOURCE_MODE_PAIR;
+    source->mode    &= ~PM_SOURCE_MODE_TEMPSUB;
+
+    // copy most data from the primary source (modelEXT, blends stay NULL)
+    pmSource *newSrc = pmSourceAlloc ();
+    newSrc->peak     = psMemIncrRefCounter (source->peak);
+    newSrc->pixels   = psMemIncrRefCounter (source->pixels);
+    newSrc->weight   = psMemIncrRefCounter (source->weight);
+    newSrc->mask     = psMemIncrRefCounter (source->mask);
+    newSrc->moments  = psMemIncrRefCounter (source->moments);
+    newSrc->modelPSF = psMemIncrRefCounter (DBL->data[1]);
+    newSrc->type     = source->type;
+    newSrc->mode     = source->mode;
+    psArrayAdd (sources, 100, newSrc);
+    psFree (newSrc);
+    psFree (DBL);
+    return true;
+}
+
+// fit a double PSF source to an extended blob
+psArray *psphotFitDBL (pmReadout *readout, pmSource *source) { 
+
+    float x, y, dx, dy;
+    pmModel *DBL;
+    pmModel *PSF;
+    psEllipseAxes axes;
+    psEllipseMoments moments;
+    psArray *modelSet;
+    
+    // make a guess at the position of the two sources
+    moments.x2 = source->moments->Sx;
+    moments.y2 = source->moments->Sy;
+    moments.xy = source->moments->Sxy;
+    axes = psEllipseMomentsToAxes (moments);
+
+    dx = 2 * cos (axes.theta);
+    dy = 2 * sin (axes.theta);
+
+    // save the PSF model from the Ensemble fit
+    PSF = source->modelPSF;
+    psphotCheckRadiusPSFBlend (readout, source, PSF, 8.0);
+    if (isnan(PSF->params->data.F32[1])) psAbort ("psphot", "nan in dbl fit");
+
+    modelSet = psArrayAlloc (2);
+    modelSet->n = 2;
+
+    DBL = pmModelCopy (PSF);
+    DBL->params->data.F32[1] *= 0.5;
+    DBL->params->data.F32[2] = source->moments->x + dx;
+    DBL->params->data.F32[3] = source->moments->y + dy;
+    modelSet->data[0] = DBL;
+
+    DBL = pmModelCopy (PSF);
+    DBL->params->data.F32[1] *= 0.5;
+    DBL->params->data.F32[2] = source->moments->x - dx;
+    DBL->params->data.F32[3] = source->moments->y - dy;
+    modelSet->data[1] = DBL;
+
+    x = source->moments->x;
+    y = source->moments->y;
+
+    // fit PSF model (set/unset the pixel mask)
+    psImageKeepCircle (source->mask, x, y, PSF->radiusTMP, "OR", PM_MASK_MARK);
+    pmSourceFitSet (source, modelSet, PM_SOURCE_FIT_PSF);
+    psImageKeepCircle (source->mask, x, y, PSF->radiusTMP, "AND", NOT_U8(PM_MASK_MARK));
+
+    return (modelSet);
+}
+
+pmModel *psphotFitEXT (pmReadout *readout, pmSource *source) { 
+
+    float x, y;
+
+    // use the source moments, etc to guess basic model parameters
+    pmModel *EXT = pmSourceModelGuess (source, modelTypeEXT); 
+    // if (isnan(EXT->params->data.F32[1])) psAbort ("psphot", "nan in ext fit");
+
+    psphotCheckRadiusEXT (readout, source, EXT);
+
+    x = EXT->params->data.F32[2];
+    y = EXT->params->data.F32[3];
+
+    if ((source->moments->Sx < 1e-3) || (source->moments->Sx < 1e-3)) {
+	psTrace ("psphotSourceFits", 5, "problem source: moments: %f %f\n", source->moments->Sx, source->moments->Sy);
+    }
+
+    // fit EXT (not PSF) model (set/unset the pixel mask)
+    psImageKeepCircle (source->mask, x, y, EXT->radiusTMP, "OR", PM_MASK_MARK);
+    pmSourceFitModel (source, EXT, PM_SOURCE_FIT_EXT);
+    psImageKeepCircle (source->mask, x, y, EXT->radiusTMP, "AND", NOT_U8(PM_MASK_MARK));
+
+    return (EXT);
+}
+
Index: /tags/RHL_v1_1/psphot/src/psphotSourceFreePixels.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotSourceFreePixels.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotSourceFreePixels.c	(revision 9533)
@@ -0,0 +1,12 @@
+# include "psphot.h"
+
+void psphotSourceFreePixels (psArray *sources) {
+
+    if (!sources) return;
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+	pmSourceFreePixels (source);
+    }
+    return;
+}
Index: /tags/RHL_v1_1/psphot/src/psphotSourceStats.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotSourceStats.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotSourceStats.c	(revision 9533)
@@ -0,0 +1,82 @@
+# include "psphot.h"
+
+// 2006.02.02 : no leaks
+psArray *psphotSourceStats (pmReadout *readout, psMetadata *recipe, psArray *peaks) 
+{
+    bool     status  = false;
+    psArray *sources = NULL;
+    float BIG_RADIUS;
+
+    psTimerStart ("psphot");
+
+    // determine properties (sky, moments) of initial sources
+    float INNER    = psMetadataLookupF32 (&status, recipe, "SKY_INNER_RADIUS");
+    float OUTER    = psMetadataLookupF32 (&status, recipe, "SKY_OUTER_RADIUS");
+    float RADIUS   = psMetadataLookupF32 (&status, recipe, "PSF_MOMENTS_RADIUS");
+    char *breakPt  = psMetadataLookupStr (&status, recipe, "BREAK_POINT");
+
+    sources = psArrayAlloc (peaks->n);
+
+    for (int i = 0; i < peaks->n; i++) {
+
+	// create a new source, add peak
+	pmSource *source = pmSourceAlloc();
+	source->peak = (pmPeak *)psMemIncrRefCounter(peaks->data[i]);
+
+	// allocate image, weight, mask arrays for each peak (square of radius OUTER)
+	pmSourceDefinePixels (source, readout, source->peak->x, source->peak->y, OUTER);
+	if (!strcasecmp (breakPt, "PEAKS")) { 
+	    psArrayAdd (sources, 100, source);
+	    psFree (source);
+	    continue;
+	}
+
+	// XXX skip faint sources?
+
+	// measure a local sky value
+	// XXX EAM : this should use ROBUST not SAMPLE median, but it is broken
+	status = pmSourceLocalSky (source, PS_STAT_SAMPLE_MEDIAN, INNER);
+	if (!status) {
+	  psFree (source);
+	  continue;
+	}
+
+	// measure the local sky variance (needed if noise is not sqrt(signal))
+	// XXX EAM : this should use ROBUST not SAMPLE median, but it is broken
+	status = pmSourceLocalSkyVariance (source, PS_STAT_SAMPLE_MEDIAN, INNER);
+	if (!status) {
+	  psFree (source);
+	  continue;
+	}
+
+	// measure basic source moments
+	status = pmSourceMoments (source, RADIUS);
+	if (status) {
+	    // add to the source array
+	    psArrayAdd (sources, 100, source);
+	    psFree (source);
+	    continue;
+	}
+
+	// if no valid pixels, or massive swing, likely saturated source,
+	// try a much larger box
+	BIG_RADIUS = PS_MIN (INNER, 3*RADIUS);
+	psTrace ("psphot", 4, "retrying moments for %d, %d\n", source->peak->x, source->peak->y);
+	status = pmSourceMoments (source, BIG_RADIUS);
+	if (status) {
+	    // add to the source array
+	    psArrayAdd (sources, 100, source);
+	    psFree (source);
+	    continue;
+	}
+
+	psFree (source);
+	continue;
+    }
+     
+    psLogMsg ("psphot", 3, "%ld moments: %f sec\n", sources->n, psTimerMark ("psphot"));
+
+    return (sources);
+}
+
+// XXX EAM : filter out bad peaks (eg, no valid pixels available for sky)
Index: /tags/RHL_v1_1/psphot/src/psphotTest.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotTest.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotTest.c	(revision 9533)
@@ -0,0 +1,84 @@
+# include "psphot.h"
+
+void psExit (int status, char *process, char *format, ...) {
+
+    va_list ap;
+
+    va_start (ap, format);
+    fprintf (stderr, "exiting %s\n", process);
+    vfprintf (stderr, format, ap);
+    va_end (ap);
+
+    exit (status);
+}
+
+int main (int argc, char **argv) {
+
+    psRegion region = {0,0,0,0};	// a region representing the entire array
+    psphotTestArguments (&argc, argv);
+
+    psFits *file = psFitsOpen (argv[1], "r");
+    psMetadata *header = psFitsReadHeader (NULL, file);
+    psImage *image = psFitsReadImage (NULL, file, region, 0);
+    psFitsClose (file);
+
+    psImageJpegColormap (argv[5]);
+
+    // psImage *fimage = psImageCopy (NULL, image, PS_TYPE_F32);
+
+    int binning = atof(argv[6]);
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN);
+    psImage *fimage = psImageRebin (NULL, image, NULL, 0, binning, stats);
+
+    float min = atof(argv[3]);
+    float max = atof(argv[4]);
+
+    psImageJpeg (fimage, argv[2], min, max);
+
+    psFree (header);
+    psFree (image);
+    exit (0);
+}
+
+
+# if (0)
+
+    psMetadata *row;
+    psArray *table;
+
+    psMetadataItem *mdi;
+
+    psMetadataConfigWrite (header, argv[2]);
+
+    // attempt to write image with NAXIS = 0
+    mdi = psMetadataLookup (header, "NAXIS");
+    mdi->data.S32 = 0;
+    mdi->type = PS_DATA_S32;
+    
+    // create a test image
+    // psImage *tmpimage = psImageAlloc (10, 10, PS_DATA_F32);
+
+    // create a test table
+    table = psArrayAlloc (10);
+table->n = 0;
+
+    for (int i = 0; i < 10; i++) {
+	row = psMetadataAlloc ();
+	psMetadataAdd (row, PS_LIST_TAIL, "ROW",   PS_DATA_S32,    "", i);
+	psMetadataAdd (row, PS_LIST_TAIL, "FROW",  PS_TYPE_F32,    "", 0.1*i);
+	psMetadataAdd (row, PS_LIST_TAIL, "DUMMY", PS_DATA_STRING, "", "test line");
+    
+	table->data[i] = row;
+    }
+    table->n = 10;
+    
+    psMetadata *theader = psMetadataAlloc ();
+    psMetadataAdd (theader, PS_LIST_HEAD, "EXTNAME", PS_DATA_STRING, "extension name", "SMPFILE");
+
+    psFits *fits = psFitsOpen (argv[3], "w");
+    // psFitsWriteImage (fits, header, tmpimage, 0);
+    psFitsWriteHeader (header, fits);
+    psFitsWriteTable (fits, theader, table);
+
+# endif
Index: /tags/RHL_v1_1/psphot/src/psphotTestArguments.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotTestArguments.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotTestArguments.c	(revision 9533)
@@ -0,0 +1,19 @@
+# include "psphot.h"
+static int usage ();
+
+void psphotTestArguments (int *argc, char **argv) {
+
+  // basic pslib options
+  psLogSetFormat ("M");
+  psArgumentVerbosity (argc, argv);
+
+  if (*argc != 7) usage ();
+
+  return;
+}
+
+static int usage () {
+
+    fprintf (stderr, "USAGE: psphotTest (input.fits) (output.jpg) (zero) (scale) (colormap) (rebin)\n");
+    exit (2);
+}
Index: /tags/RHL_v1_1/psphot/src/psphotVersion.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotVersion.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotVersion.c	(revision 9533)
@@ -0,0 +1,6 @@
+# include "psphot.h"
+
+const char *psphotCVSName(void) {
+   static char *name = "$Name: not supported by cvs2svn $";
+   return(name);
+}
Index: /tags/RHL_v1_1/psphot/src/psphotWeightBias.c
===================================================================
--- /tags/RHL_v1_1/psphot/src/psphotWeightBias.c	(revision 9533)
+++ /tags/RHL_v1_1/psphot/src/psphotWeightBias.c	(revision 9533)
@@ -0,0 +1,78 @@
+# include "psphot.h"
+
+// select objects fitted with PSF model
+// re-fit all of them with the non-poisson errors
+// only allow the normalization to vary 
+// XXX eventually, we should be able to do this with linear fitting...
+bool psphotWeightBias (pmReadout *readout, psArray *sources, psMetadata *recipe, pmPSF *psf) { 
+
+    int Nfit = 0;
+    bool status;
+    psF32 *PARp, *PARc;
+    psF32 x, y;
+
+    psTimerStart ("psphot");
+
+    // source analysis is done in S/N order (brightest first)
+    sources = psArraySort (sources, psphotSortBySN);
+    
+    // set fitting method to use non-poisson errors (local sky error)
+    pmSourceFitModelInit (15, 0.1, false);
+    
+    // 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 ("psphot", "analysis region mis-defined");
+
+    FILE *f = fopen ("bias.dat", "w");
+    if (f == NULL) psAbort ("psphot", "can't open output file");
+
+    for (int i = 0; i < sources->n; i++) {
+	pmSource *source = sources->data[i];
+
+	// skip lower-quality objects
+	if (source->type != PM_SOURCE_TYPE_STAR) continue; 
+	if (source->mode &  PM_SOURCE_MODE_POOR) continue;
+	if (source->mode &  PM_SOURCE_MODE_FAIL) continue;
+	if (source->mode &  PM_SOURCE_MODE_PAIR) continue;
+	if (source->mode &  PM_SOURCE_MODE_BLEND) continue;
+	if (source->mode &  PM_SOURCE_MODE_SATSTAR) continue;
+
+	// if model is NULL, we don't have a starting guess
+	if (source->modelPSF == NULL) continue;
+
+	if (source->moments->x < AnalysisRegion.x0) continue;
+	if (source->moments->y < AnalysisRegion.y0) continue;
+	if (source->moments->x > AnalysisRegion.x1) continue;
+	if (source->moments->y > AnalysisRegion.y1) continue;
+
+	// replace object in image
+	pmModelAdd (source->pixels, source->mask, source->modelPSF, false, false);
+
+	// make a temporary model (we don't keep the result of this analysis)
+	pmModel *PSF = pmModelCopy (source->modelPSF);
+
+	// extend source radius as needed
+	psphotCheckRadiusPSF (readout, source, PSF);
+
+	x = PSF->params->data.F32[2];
+	y = PSF->params->data.F32[3];
+
+	// fit PSF model (set/unset the pixel mask)
+	psImageKeepCircle (source->mask, x, y, PSF->radiusTMP, "OR", PM_MASK_MARK);
+	pmSourceFitModel (source, PSF, PM_SOURCE_FIT_NORM);
+	psImageKeepCircle (source->mask, x, y, PSF->radiusTMP, "AND", NOT_U8(PM_MASK_MARK));
+    
+	// re-subtract PSF for object, leave local sky
+	pmModelSub (source->pixels, source->mask, source->modelPSF, false, false);
+
+	PARp = source->modelPSF->params->data.F32;
+	PARc = PSF->params->data.F32;
+	fprintf (f, "%7.1f %7.1f %9.2f %9.2f %10.3f\n", PARp[2], PARp[3], PARp[1], PARc[1], source->moments->dSky);
+	Nfit ++;
+    }
+    fclose (f);
+    psLogMsg ("psphot", 3, "measure PSF weighting bias for %d objects: %f sec\n", Nfit, psTimerMark ("psphot"));
+
+    return (true);
+}
