Index: /tags/rel11_ifa_pre0/psModules/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/.cvsignore	(revision 6961)
@@ -0,0 +1,27 @@
+autom4te.cache
+Makefile
+config.log
+config.status
+libtool
+Makefile.in
+aclocal.m4
+configure
+Doxyfile
+DoxygenLog
+docs
+man
+psmodule-config
+psmodule.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/rel11_ifa_pre0/psModules/AUTHORS
===================================================================
--- /tags/rel11_ifa_pre0/psModules/AUTHORS	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/AUTHORS	(revision 6961)
@@ -0,0 +1,1 @@
+Maui High Performance Computing Center, University of Hawai'i
Index: /tags/rel11_ifa_pre0/psModules/COPYING
===================================================================
--- /tags/rel11_ifa_pre0/psModules/COPYING	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/COPYING	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/ChangeLog
===================================================================
--- /tags/rel11_ifa_pre0/psModules/ChangeLog	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/ChangeLog	(revision 6961)
@@ -0,0 +1,1 @@
+ 
Index: /tags/rel11_ifa_pre0/psModules/Doxyfile.in
===================================================================
--- /tags/rel11_ifa_pre0/psModules/Doxyfile.in	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/Doxyfile.in	(revision 6961)
@@ -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/psmodule
+
+# 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/rel11_ifa_pre0/psModules/INSTALL
===================================================================
--- /tags/rel11_ifa_pre0/psModules/INSTALL	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/INSTALL	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/Makefile.am	(revision 6961)
@@ -0,0 +1,34 @@
+SUBDIRS = src test
+
+bin_SCRIPTS = psmodule-config
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= psmodule.pc
+
+EXTRA_DIST = \
+	Doxyfile.in \
+	psmodule-config.in \
+	psmodule.pc.in \
+	autogen.sh
+
+if DOXYGEN
+
+docs: Doxyfile $(prefix)/docs/psmodule $(mandir)/man3
+	doxygen Doxyfile
+	mv -f $(prefix)/docs/psmodule/man/man3/* $(mandir)/man3
+	rm -rf $(prefix)/docs/psmodule/man
+
+$(prefix)/docs/psmodule:
+	mkdir -p $(prefix)/docs/psmodule
+
+$(mandir)/man3:
+	mkdir -p $(mandir)/man3
+
+uninstall-hook:
+	rm -rf $(prefix)/docs/psmodule/*
+
+CLEANFILES = $(prefix)/docs/psmodule/*
+
+endif
+
+test: check
Index: /tags/rel11_ifa_pre0/psModules/NEWS
===================================================================
--- /tags/rel11_ifa_pre0/psModules/NEWS	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/NEWS	(revision 6961)
@@ -0,0 +1,1 @@
+ 
Index: /tags/rel11_ifa_pre0/psModules/README
===================================================================
--- /tags/rel11_ifa_pre0/psModules/README	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/README	(revision 6961)
@@ -0,0 +1,1 @@
+
Index: /tags/rel11_ifa_pre0/psModules/TODO
===================================================================
--- /tags/rel11_ifa_pre0/psModules/TODO	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/TODO	(revision 6961)
@@ -0,0 +1,1 @@
+ 
Index: /tags/rel11_ifa_pre0/psModules/autogen.sh
===================================================================
--- /tags/rel11_ifa_pre0/psModules/autogen.sh	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/autogen.sh	(revision 6961)
@@ -0,0 +1,106 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=psmodule
+TEST_TYPE=-f
+FILE=psmodule.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"
+
+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/rel11_ifa_pre0/psModules/configure.ac
===================================================================
--- /tags/rel11_ifa_pre0/psModules/configure.ac	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/configure.ac	(revision 6961)
@@ -0,0 +1,135 @@
+AC_PREREQ(2.59)
+
+AC_INIT([psmodule],[0.9.99],[http://pan-starrs.ifa.hawaii.edu/bugzilla])
+AC_CONFIG_SRCDIR([psmodule.pc.in])
+
+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
+
+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="astrom config detrend imcombine imsubtract objects pslib"
+# 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/libpsmodule\1.la|g"`
+AC_SUBST(SRCSUBLIBS,${SRCSUBLIBS=})
+AC_SUBST(SRCINC,${SRCINC=})
+AC_SUBST([SRCDIRS],${SRCDIRS=})
+
+dnl handle debug building
+AC_ARG_ENABLE(optimize,
+  [AS_HELP_STRING(--enable-optimize,enable compiler optimization)],
+  [AC_MSG_RESULT(compile optimization enabled)
+   CFLAGS="${CFLAGS=} -O2"],
+  [AC_MSG_RESULT([compile optimization disabled])
+   CFLAGS="${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 CFLAGS="${CFLAGS=} -fprofile-arcs -ftest-coverage -pg"]
+
+dnl doxygen -------------------------------------------------------------------
+
+AC_CHECK_PROG([doxygen], [doxygen], [true], [false])
+AM_CONDITIONAL(DOXYGEN, test x$doxygen = xtrue)
+
+dnl libjpeg -------------------------------------------------------------------
+
+AC_CHECK_LIB(jpeg,jpeg_CreateCompress,[],[AC_MSG_ERROR([jpeg library not found.])])
+
+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.9.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
+
+PSMODULE_CFLAGS="${PSMODULE_CFLAGS=} ${PSLIB_CFLAGS}"
+PSMODULE_LIBS="${PSMODULE_CFLAGS=} ${PSLIB_LIBS}"
+dnl ---------------------------------------------------------------------------
+
+AC_SUBST([PSMODULE_CFLAGS])
+AC_SUBST([PSMODULE_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  src/astrom/Makefile
+  src/config/Makefile
+  src/detrend/Makefile
+  src/imcombine/Makefile
+  src/imsubtract/Makefile
+  src/objects/Makefile
+  src/pslib/Makefile
+  test/Makefile
+  test/astrom/Makefile
+  test/config/Makefile
+  test/detrend/Makefile
+  test/imcombine/Makefile
+  test/imsubtract/Makefile
+  test/objects/Makefile
+  test/pslib/Makefile
+  Doxyfile
+  psmodule-config
+  psmodule.pc
+])
+
+AC_OUTPUT
Index: /tags/rel11_ifa_pre0/psModules/psmodule-config.in
===================================================================
--- /tags/rel11_ifa_pre0/psModules/psmodule-config.in	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/psmodule-config.in	(revision 6961)
@@ -0,0 +1,76 @@
+#! /bin/sh
+
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+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} @PSMODULE_CFLAGS@
+       	;;
+
+    --libs)
+       	echo -L${libdir} -lpsmodule @PSMODULE_LIBS@
+       	;;
+
+    --deps)
+       	echo @LDFLAGS@
+       	;;
+    *)
+	usage
+	exit 1
+	;;
+    esac
+    shift
+done
+
+exit 0
Index: /tags/rel11_ifa_pre0/psModules/psmodule.kdevelop
===================================================================
--- /tags/rel11_ifa_pre0/psModules/psmodule.kdevelop	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/psmodule.kdevelop	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/psmodule.kdevses
===================================================================
--- /tags/rel11_ifa_pre0/psModules/psmodule.kdevses	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/psmodule.kdevses	(revision 6961)
@@ -0,0 +1,50 @@
+<?xml version = '1.0' encoding = 'UTF-8'?>
+<!DOCTYPE KDevPrjSession>
+<KDevPrjSession>
+ <DocsAndViews NumberOfDocuments="10" >
+  <Doc0 NumberOfViews="1" URL="file:///home/drobbin/panstarrs/temp/psModule/src/astrom/pmAstrometry.h" >
+   <View0 line="101" Type="Source" />
+  </Doc0>
+  <Doc1 NumberOfViews="1" URL="file:///home/drobbin/panstarrs/temp/psModule/src/astrom/pmAstrometryObjects.c" >
+   <View0 line="254" Type="Source" />
+  </Doc1>
+  <Doc2 NumberOfViews="1" URL="file:///home/drobbin/panstarrs/temp/psModule/src/imcombine/pmImageCombine.c" >
+   <View0 line="114" Type="Source" />
+  </Doc2>
+  <Doc3 NumberOfViews="1" URL="file:///home/drobbin/panstarrs/temp/psModule/test/astrom/tst_pmAstrometry01.c" >
+   <View0 line="342" Type="Source" />
+  </Doc3>
+  <Doc4 NumberOfViews="1" URL="file:///home/drobbin/panstarrs/temp/psModule/src/astrom/pmAstrometry.c" >
+   <View0 line="356" Type="Source" />
+  </Doc4>
+  <Doc5 NumberOfViews="1" URL="file:///home/drobbin/panstarrs/temp/psModule/src/imsubtract/pmImageSubtract.c" >
+   <View0 line="359" Type="Source" />
+  </Doc5>
+  <Doc6 NumberOfViews="1" URL="file:///home/drobbin/panstarrs/temp/psModule/src/objects/pmPSF.c" >
+   <View0 line="92" Type="Source" />
+  </Doc6>
+  <Doc7 NumberOfViews="1" URL="file:///home/drobbin/panstarrs/temp/psModule/src/objects/pmObjects.c" >
+   <View0 line="1912" Type="Source" />
+  </Doc7>
+  <Doc8 NumberOfViews="1" URL="file:///home/drobbin/panstarrs/temp/psModule/src/objects/pmObjects.h" >
+   <View0 line="77" Type="Source" />
+  </Doc8>
+  <Doc9 NumberOfViews="1" URL="file:///home/drobbin/panstarrs/temp/psModule/src/objects/pmPSFtry.c" >
+   <View0 line="60" Type="Source" />
+  </Doc9>
+ </DocsAndViews>
+ <pluginList>
+  <kdevdebugger>
+   <breakpointList/>
+  </kdevdebugger>
+  <kdevbookmarks>
+   <bookmarks/>
+  </kdevbookmarks>
+  <kdevvalgrind>
+   <executable path="" params="" />
+   <valgrind path="" params="" />
+   <calltree path="" params="" />
+   <kcachegrind path="" />
+  </kdevvalgrind>
+ </pluginList>
+</KDevPrjSession>
Index: /tags/rel11_ifa_pre0/psModules/psmodule.pc.in
===================================================================
--- /tags/rel11_ifa_pre0/psModules/psmodule.pc.in	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/psmodule.pc.in	(revision 6961)
@@ -0,0 +1,10 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: psmodule
+Description: Pan-STARRS Module Library
+Version: @VERSION@
+Libs: -L${libdir} -lpsmodule @PSMODULE_LIBS@
+Cflags: -I${includedir} @PSMODULE_CFLAGS@
Index: /tags/rel11_ifa_pre0/psModules/src/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/.cvsignore	(revision 6961)
@@ -0,0 +1,9 @@
+.deps
+.libs
+Makefile
+config.h
+stamp-h1
+Makefile.in
+config.h.in
+*.la
+*.lo
Index: /tags/rel11_ifa_pre0/psModules/src/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/Makefile.am	(revision 6961)
@@ -0,0 +1,12 @@
+SUBDIRS = $(SRCDIRS)
+lib_LTLIBRARIES = libpsmodule.la
+
+libpsmodule_la_SOURCES = dummy.c
+libpsmodule_la_CPPFLAGS = $(SRCINC)
+libpsmodule_la_LIBADD = $(SRCSUBLIBS)
+libpsmodule_la_DEPENDENCIES = $(SRCSUBLIBS)
+
+include_HEADERS = \
+	psmodules.h
+
+EXTRA_DIST = psErrorCodes.dat
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/.cvsignore	(revision 6961)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/Makefile.am	(revision 6961)
@@ -0,0 +1,52 @@
+noinst_LTLIBRARIES = libpsmoduleastrom.la
+
+libpsmoduleastrom_la_CPPFLAGS = $(SRCINC) $(PSMODULE_CFLAGS) -I../pslib/
+libpsmoduleastrom_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmoduleastrom_la_SOURCES  = \
+	pmFPA.c \
+	pmFPAConstruct.c \
+	pmFPACopy.c \
+	pmFPAHeader.c \
+	pmFPARead.c \
+	pmFPAUtils.c \
+	pmFPAWrite.c \
+	pmHDU.c \
+	pmHDUUtils.c \
+	pmReadout.c \
+	pmConcepts.c \
+	pmConceptsRead.c \
+	pmConceptsWrite.c \
+	pmConceptsStandard.c \
+	pmFPA_JPEG.c \
+	pmFPAview.c \
+	pmFPAfile.c
+
+#	pmFPAMaskWeight.c \
+#	pmFPAAstrometry.c
+#	pmAstrometryObjects.c
+#	pmChipMosaic.c
+
+psmoduleincludedir = $(includedir)
+psmoduleinclude_HEADERS = \
+	pmFPA.h \
+	pmFPAConstruct.h \
+	pmFPACopy.h \
+	pmFPAHeader.h \
+	pmFPARead.h \
+	pmFPAUtils.h \
+	pmFPAWrite.h \
+	pmHDU.h \
+	pmHDUUtils.h \
+	pmReadout.h \
+	pmConcepts.h \
+	pmConceptsRead.h \
+	pmConceptsWrite.h \
+	pmConceptsStandard.h \
+	pmFPA_JPEG.h \
+	pmFPAview.h \
+	pmFPAfile.h
+
+#	pmFPAMaskWeight.h \
+#	pmFPAAstrometry.h
+#	pmAstrometryObjects.h
+#	pmChipMosaic.h 
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmAstrometry.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmAstrometry.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmAstrometry.c	(revision 6961)
@@ -0,0 +1,534 @@
+/** @file  psAstrometry.c
+*
+*  @brief This file defines the basic types for astronomical coordinate
+*  transformation
+*
+*  @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.14 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-04-17 18:10:08 $
+*
+*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+*/
+
+/******************************************************************************/
+/*  INCLUDE FILES                                                             */
+/******************************************************************************/
+#include <string.h>
+#include <math.h>
+#include "pslib.h"
+
+#include "pmAstrometry.h"
+#include "pmConcepts.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);
+}
+
+/******************************************************************************
+ *****************************************************************************/
+#define PS_FREE_HIERARCHY 1
+static void readoutFree(pmReadout *readout)
+{
+    if (readout != NULL) {
+        psFree(readout->image);
+        psFree(readout->mask);
+        psFree(readout->weight);
+        psFree(readout->analysis);
+        #if 0
+
+        psFree(readout->parent);
+        #endif
+
+        readout->parent = NULL;
+    }
+}
+
+static void cellFree(pmCell *cell)
+{
+    if (cell != NULL) {
+        psFree(cell->toChip);
+        psFree(cell->toFPA);
+        psFree(cell->toSky);
+        psFree(cell->concepts);
+        psFree(cell->analysis);
+        psFree(cell->camera);
+        //
+        // Set the parent to NULL in all cell->readouts before psFree(cell->readouts)
+        // in order to avoid memory reference counter problems.
+        //
+        #if 0
+
+        for (psS32 i = 0 ; i < cell->readouts->n ; i++) {
+            pmReadout *tmpReadout = (pmReadout *) cell->readouts->data[i];
+            tmpReadout->parent = NULL;
+            if (PS_FREE_HIERARCHY == 1) {
+                psFree(tmpReadout);
+            }
+        }
+        psFree(cell->parent);
+        #endif
+
+        cell->parent = NULL;
+
+        psFree(cell->readouts);
+        psFree(cell->hdu);
+
+    }
+}
+
+static void chipFree(pmChip* chip)
+{
+    if (chip != NULL) {
+        psFree(chip->toFPA);
+        psFree(chip->fromFPA);
+        psFree(chip->concepts);
+        psFree(chip->analysis);
+        //
+        // Set the parent to NULL in all chip->cells before psFree(chip->cells)
+        // in order to avoid memory reference counter problems.
+        //
+        #if 0
+
+        for (psS32 i = 0 ; i < chip->cells->n ; i++) {
+            pmCell *tmpCell = (pmCell *) chip->cells->data[i];
+            tmpCell->parent = NULL;
+            if (PS_FREE_HIERARCHY == 1) {
+                psFree(tmpCell);
+            }
+        }
+        psFree(chip->parent);
+        #endif
+
+        chip->parent = NULL;
+        psFree(chip->cells);
+        psFree(chip->hdu);
+    }
+}
+
+
+static void FPAFree(pmFPA *fpa)
+{
+    if (fpa != NULL) {
+        psFree(fpa->fromTangentPlane);
+        psFree(fpa->toTangentPlane);
+        psFree(fpa->projection);
+        psFree(fpa->concepts);
+        psFree(fpa->analysis);
+        psFree(fpa->camera);
+        //
+        // Set the parent to NULL in all fpa->chips before psFree(fpa->chips)
+        // in order to avoid memory reference counter problems.
+        //
+        #if 0
+
+        for (psS32 i = 0 ; i < fpa->chips->n ; i++) {
+            pmChip *tmpChip = (pmChip *) fpa->chips->data[i];
+            tmpChip->parent = NULL;
+            if (PS_FREE_HIERARCHY == 1) {
+                psFree(tmpChip);
+            }
+        }
+        #endif
+        psFree(fpa->chips);
+        psFree(fpa->hdu);
+        psFree(fpa->phu);
+    }
+}
+
+void p_pmHDUFree(p_pmHDU *hdu)
+{
+    if (hdu) {
+        psFree(hdu->extname);
+        psFree(hdu->header);
+        psFree(hdu->images);
+        psFree(hdu->masks);
+        psFree(hdu->weights);
+    }
+}
+
+// XXX: Verify these default values for row0, col0, rowBins, colBins
+// PAP: These values may disappear in the future in favour of values in parent->concepts?
+pmReadout *pmReadoutAlloc(pmCell *cell)
+{
+    pmReadout *tmpReadout = (pmReadout *) psAlloc(sizeof(pmReadout));
+
+    tmpReadout->col0 = 0;
+    tmpReadout->row0 = 0;
+    tmpReadout->colBins = 0;
+    tmpReadout->rowBins = 0;
+    tmpReadout->image = NULL;
+    tmpReadout->mask = NULL;
+    tmpReadout->weight = NULL;
+    tmpReadout->analysis = psMetadataAlloc();
+    tmpReadout->parent = cell;
+    if (cell != NULL) {
+        cell->readouts = psArrayAdd(cell->readouts, 1, (psPtr) tmpReadout);
+    }
+    psMemSetDeallocator(tmpReadout, (psFreeFunc) readoutFree);
+    return(tmpReadout);
+}
+
+// XXX: Verify these default values for row0, col0.
+// PAP: These values may disappear in the future in favour of values in the "concepts"?
+pmCell *pmCellAlloc(
+    pmChip *chip,
+    psMetadata *cameraData,
+    const char *name)
+{
+    pmCell *tmpCell = (pmCell *) psAlloc(sizeof(pmCell));
+
+    tmpCell->col0 = 0;
+    tmpCell->row0 = 0;
+    tmpCell->toChip = NULL;
+    tmpCell->toFPA = NULL;
+    tmpCell->toSky = NULL;
+    tmpCell->concepts = psMetadataAlloc();
+    psBool rc = psMetadataAddStr(tmpCell->concepts, PS_LIST_HEAD, "CELL.NAME", 0, NULL, name);
+    if (rc == false) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CELL.NAME to metadata.\n");
+    }
+    tmpCell->camera = psMemIncrRefCounter(cameraData);
+    tmpCell->analysis = psMetadataAlloc();
+    tmpCell->readouts = psArrayAlloc(0);
+    tmpCell->parent = chip;
+    if (chip != NULL) {
+        chip->cells = psArrayAdd(chip->cells, 1, (psPtr) tmpCell);
+    }
+    tmpCell->process = true;            // All cells are processed by default
+    tmpCell->exists = false;            // Not yet read in
+    tmpCell->hdu = NULL;
+
+    pmConceptsBlankCell(tmpCell);
+
+    psMemSetDeallocator(tmpCell, (psFreeFunc) cellFree);
+    return(tmpCell);
+}
+
+// XXX: Verify these default values for row0, col0.
+// PAP: row0, col0 may disappear in the future in favour of storing values in the "concepts".
+pmChip *pmChipAlloc(
+    pmFPA *fpa,
+    const char *name)
+{
+    pmChip *tmpChip = (pmChip *) psAlloc(sizeof(pmChip));
+
+    tmpChip->col0 = 0;
+    tmpChip->row0 = 0;
+    tmpChip->toFPA = NULL;
+    tmpChip->fromFPA = NULL;
+    tmpChip->concepts = psMetadataAlloc();
+    psBool rc = psMetadataAddStr(tmpChip->concepts, PS_LIST_HEAD, "CHIP.NAME", 0, NULL, name);
+    if (rc == false) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CHIP.NAME to metadata.\n");
+    }
+    tmpChip->analysis = psMetadataAlloc();
+    tmpChip->cells = psArrayAlloc(0);
+    tmpChip->parent = fpa;
+    if (fpa != NULL) {
+        fpa->chips = psArrayAdd(fpa->chips, 1, (psPtr) tmpChip);
+    }
+    tmpChip->process = true;            // Work on all chips, by default
+    tmpChip->exists = false;            // Not read in yet
+    tmpChip->hdu = NULL;
+
+    pmConceptsBlankChip(tmpChip);
+
+    psMemSetDeallocator(tmpChip, (psFreeFunc) chipFree);
+    return(tmpChip);
+}
+
+pmFPA *pmFPAAlloc(const psMetadata *camera)
+{
+    pmFPA *tmpFPA = (pmFPA *) psAlloc(sizeof(pmFPA));
+
+    tmpFPA->fromTangentPlane = NULL;
+    tmpFPA->toTangentPlane = NULL;
+    tmpFPA->projection = NULL;
+    tmpFPA->concepts = psMetadataAlloc();
+    tmpFPA->analysis = NULL;
+    tmpFPA->camera = psMemIncrRefCounter((psPtr)camera);
+    tmpFPA->chips = psArrayAlloc(0);
+    tmpFPA->hdu = NULL;
+    tmpFPA->phu = NULL;
+
+    pmConceptsBlankFPA(tmpFPA);
+
+    psMemSetDeallocator(tmpFPA, (psFreeFunc) FPAFree);
+    return(tmpFPA);
+}
+
+p_pmHDU *p_pmHDUAlloc(const char *extname)
+{
+    p_pmHDU *hdu = psAlloc(sizeof(p_pmHDU));
+    psMemSetDeallocator(hdu, (psFreeFunc)p_pmHDUFree);
+
+    hdu->extname = psStringCopy(extname);
+    hdu->header = NULL;
+    hdu->images = NULL;
+    hdu->masks = NULL;
+    hdu->weights = NULL;
+
+    return hdu;
+}
+
+static psBool cellCheckParents(pmCell *cell)
+{
+    if (cell == NULL) {
+        return(true);
+    }
+    psBool flag = true;
+
+    for (psS32 i = 0 ; i < cell->readouts->n ; i++) {
+        pmReadout *tmpReadout = (pmReadout *) cell->readouts->data[i];
+        PS_ASSERT_PTR_NON_NULL(tmpReadout, false);
+        if (tmpReadout->parent != cell) {
+            tmpReadout->parent = cell;
+            flag = false;
+        }
+    }
+    return(flag);
+}
+
+static psBool chipCheckParents(pmChip *chip)
+{
+    if (chip == NULL) {
+        return(true);
+    }
+    psBool flag = true;
+
+    for (psS32 i = 0 ; i < chip->cells->n ; i++) {
+        pmCell *tmpCell = (pmCell *) chip->cells->data[i];
+        PS_ASSERT_PTR_NON_NULL(tmpCell, false);
+        if (tmpCell->parent != chip) {
+            tmpCell->parent = chip;
+            flag = false;
+        }
+
+        flag &= cellCheckParents(tmpCell);
+    }
+    return(flag);
+}
+
+psBool pmFPACheckParents(pmFPA *fpa)
+{
+    if (fpa == NULL) {
+        return(true);
+    }
+    psBool flag = true;
+
+    for (psS32 i = 0 ; i < fpa->chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) fpa->chips->data[i];
+        PS_ASSERT_PTR_NON_NULL(tmpChip, false);
+        if (tmpChip->parent != fpa) {
+            tmpChip->parent = fpa;
+            flag = false;
+        }
+
+        flag &= chipCheckParents(tmpChip);
+    }
+    return(flag);
+}
+
+
+
+/*****************************************************************************
+ *****************************************************************************/
+
+// 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;
+}
+
+/*****************************************************************************
+ *****************************************************************************/
+bool pmFPASelectChip(
+    pmFPA *fpa,
+    int chipNum)
+{
+    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 {
+            tmpChip->process = false;
+            setCellsProcess(tmpChip, 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, false);
+
+    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);
+}
+
+
+bool pmCellSetWeights(pmCell *cell // Cell for which to set weights
+                     )
+{
+    float gain = psMetadataLookupF32(NULL, cell->concepts, "CELL.GAIN"); // Cell gain
+    float readnoise = psMetadataLookupF32(NULL, cell->concepts, "CELL.READNOISE"); // Cell read noise
+    psRegion *trimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC"); // Trim section
+
+    p_pmHDU *hdu = cell->hdu;           // The data unit, containing the weight and mask originals
+    if (!hdu) {
+        pmChip *chip = cell->parent;    // The parent chip
+        if (chip->hdu) {
+            hdu = chip->hdu;
+        } else {
+            pmFPA *fpa = chip->parent;  // The parent FPA
+            hdu = fpa->hdu;
+            if (!hdu) {
+                psError(PS_ERR_UNKNOWN, true, "Unable to find the HDU in the hierarchy!\n");
+                return false;
+            }
+        }
+    }
+
+    psArray *pixels = hdu->images;      // Array of images
+    psArray *weights = hdu->weights;    // Array of weight images
+    psArray *masks = hdu->masks;        // Array of mask images
+    // Generate the weights and masks if required
+    if (! weights) {
+        weights = psArrayAlloc(pixels->n);
+        for (int i = 0; i < pixels->n; i++) {
+            psImage *image = pixels->data[i];
+            weights->data[i] = psImageAlloc(image->numCols, image->numRows, PS_TYPE_F32);
+            psImageInit(weights->data[i], 0.0);
+        }
+        hdu->weights = weights;
+    }
+    if (! masks) {
+        masks = psArrayAlloc(pixels->n);
+        for (int i = 0; i < pixels->n; i++) {
+            psImage *image = pixels->data[i];
+            masks->data[i] = psImageAlloc(image->numCols, image->numRows, PS_TYPE_U8);
+            psImageInit(masks->data[i], 0);
+        }
+        hdu->masks = masks;
+    }
+
+    // Set the pixels
+    psArray *readouts = cell->readouts; // Array of readouts
+    for (int i = 0; i < readouts->n; i++) {
+        pmReadout *readout = readouts->data[i]; // The readout of interest
+
+        if (! readout->weight) {
+            readout->weight = weights->data[i];
+        }
+        if (! readout->mask) {
+            readout->mask = masks->data[i];
+        }
+
+        // Mask is already set to 0
+
+        // Set weight image to the variance = g*f + rn^2
+        psImage *image = psImageSubset(readout->image, *trimsec); // The pixels
+        psImage *weight = psImageSubset(readout->weight, *trimsec); // The weight map
+        psBinaryOp(weight, image, "*", psScalarAlloc(gain, PS_TYPE_F32));
+        psBinaryOp(weight, weight, "+", psScalarAlloc(readnoise*readnoise, PS_TYPE_F32));
+    }
+
+    return true;
+}
+
+
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmAstrometry.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmAstrometry.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmAstrometry.h	(revision 6961)
@@ -0,0 +1,467 @@
+/** @file  pmAstrometry.h
+*
+*  @brief This file defines the basic types for astronomical coordinate
+*  transformation
+*
+*  @ingroup AstroImage
+*
+*  @author GLG, MHPCC
+*
+*  @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-04-17 18:10:08 $
+*
+*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+*/
+
+#ifndef PS_ASTROMETRY_H
+#define PS_ASTROMETRY_H
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+#include "pslib.h"
+#include "psDB.h"
+
+/// @addtogroup AstroImage
+/// @{
+
+// XXX: Is this correct?  Must determine what p_pmHDU is.
+// XXX: Create the p_pmHDU alloc/free.
+typedef struct
+{
+    const char *extname;                // Extension name, if it corresponds to this level
+    psMetadata *header;                 // The FITS header, if it corresponds to this level
+    psArray *images;                    // The pixel data, if it corresponds to this level
+    psArray *masks;                     // The mask data, if it corresponds to this level
+    psArray *weights;                   // The weight data, if it corresponds to this level
+}
+p_pmHDU;
+
+/** 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
+{
+    // 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
+    // Information
+    psMetadata *concepts;               ///< Cache for PS concepts
+    psMetadata *analysis;               ///< FPA-level analysis metadata
+    const psMetadata *camera;           ///< Camera configuration
+    psArray *chips;                     ///< The chips
+    p_pmHDU *hdu;                       ///< FITS data
+    psMetadata *phu;                    ///< Primary Header
+}
+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
+{
+    // 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
+    // Information
+    psMetadata *concepts;               ///< Cache for PS concepts
+    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 exists;                        ///< Does the chip exist --- has it been read in?
+    p_pmHDU *hdu;                       ///< FITS data
+}
+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
+{
+    // Offset specifying position on chip
+    int col0;                           ///< Offset from the left of chip.
+    int row0;                           ///< Offset from the bottom of chip.
+    // Astrometric transformations
+    psPlaneTransform* toChip;           ///< Transformations from cell to chip coordinates
+    psPlaneTransform* toFPA;            ///< Transformations from cell to FPA coordinates
+    psPlaneTransform* toSky;            ///< Transformations from cell to sky coordinates
+    // Information
+    psMetadata *concepts;               ///< Cache for PS concepts
+    psMetadata *camera;                 ///< Camera 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 exists;                        ///< Does the cell exist --- has it been read in?
+    p_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
+{
+    // Position on the cell
+    // XXX: These may be removed in the future; use parent->concepts instead?
+    int col0;                           ///< Offset from the left of chip.
+    int row0;                           ///< Offset from the bottom of chip.
+    int colBins;                        ///< Amount of binning in x-dimension
+    int rowBins;                        ///< Amount of binning in y-dimension
+    // Information
+    psImage *image;                     ///< Imaging area of readout
+    psImage *mask;                      ///< Mask of input image
+    psImage *weight;                    ///< Weight of input image
+    psMetadata *analysis;               ///< Readout-level analysis metadata
+    pmCell *parent;                     ///< Parent cell
+}
+pmReadout;
+
+
+/** 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
+    psMetadata *cameraData, ///< Camera data
+    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
+);
+
+/** Allocates a p_pmHDU
+ *
+ * XXX: More detailed description
+ *
+ * @return p_pmHDU*    newly allocated p_pmHDU
+ */
+p_pmHDU *p_pmHDUAlloc(const char *extname // Extension name
+                     );
+
+
+/** 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
+);
+
+
+
+/** FUNC DESC
+ *
+ *
+ *
+ *
+ */
+
+
+
+/**
+ *
+ * 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
+);
+
+/**
+ *
+ * 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
+);
+
+
+/*****************************************************************************
+    Old Stuff
+ *****************************************************************************/
+
+
+
+/** 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 // #ifndef PS_ASTROMETRY_H
+
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmAstrometryObjects.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmAstrometryObjects.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmAstrometryObjects.c	(revision 6961)
@@ -0,0 +1,630 @@
+/** @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.3 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-04-17 18:01:04 $
+*
+*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+*/
+
+/******************************************************************************/
+/*  INCLUDE FILES                                                             */
+/******************************************************************************/
+#include <string.h>
+#include <math.h>
+#include "pslib.h"
+#include "pmFPA.h"
+#include "pmAstrometryObjects.h"
+
+
+#define PM_ASTROMETRYOBJECTS_DEBUG 1
+/******************************************************************************
+pmAstromObjSortByFPX(**a, **b): sort by mag (descending)
+ 
+Is this a private routine?
+Should we do the early asserts?
+ ******************************************************************************/
+
+int pmAstromObjSortByFPX(
+    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->FP.x - B->FP.x;
+    if (diff > FLT_EPSILON) {
+        return (+1);
+    }
+
+    if (diff < FLT_EPSILON) {
+        return (-1);
+    }
+    return (0);
+}
+
+
+
+/******************************************************************************
+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);
+}
+
+
+/******************************************************************************
+pmAstromRadiusMatch(st1, st2, config)
+ ******************************************************************************/
+psArray *pmAstromRadiusMatch(
+    psArray *st1,
+    psArray *st2,
+    psMetadata *config)
+{
+    PS_ASSERT_PTR_NON_NULL(st1, NULL);
+    PS_ASSERT_PTR_NON_NULL(st2, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    bool status;
+    int jStart;
+    double dX, dY, dR;
+
+    double RADIUS = psMetadataLookupF32 (&status, config, "PSASTRO.MATCH.RADIUS");
+    double RADIUS_SQR = PS_SQR (RADIUS);
+
+    // sort both lists by Focal Plane X coord
+    st1 = psArraySort (st1, pmAstromObjSortByFPX);
+    st2 = psArraySort (st2, pmAstromObjSortByFPX);
+
+    psArray *matches = psArrayAlloc (100);
+    matches->n = 0;
+
+    int i = 0;
+    int j = 0;
+    while ((i < st1->n) && (j < st2->n)) {
+
+        dX = ((pmAstromObj *)st1->data[i])->FP.x - ((pmAstromObj *)st2->data[j])->FP.x;
+        if (dX < -RADIUS) {
+            i++;
+            continue;
+        }
+        if (dX > +RADIUS) {
+            j++;
+            continue;
+        }
+
+        jStart = j;
+        while ((dX > -RADIUS) && (j < st2->n)) {
+
+            dX = ((pmAstromObj *)st1->data[i])->FP.x - ((pmAstromObj *)st2->data[j])->FP.x;
+            dY = ((pmAstromObj *)st1->data[i])->FP.y - ((pmAstromObj *)st2->data[j])->FP.y;
+            dR = dX*dX + dY*dY;
+
+            if (dR > RADIUS_SQR) {
+                j++;
+                continue;
+            }
+
+            // got a match; add to output list
+            pmAstromMatch *match = pmAstromMatchAlloc (i, j);
+            psArrayAdd (matches, 100, match);
+
+            j++;
+        }
+        j = jStart;
+        i++;
+    }
+    return (matches);
+}
+
+
+
+/******************************************************************************
+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)
+{
+    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->i1];
+        refStar = ref->data[pair->i2];
+
+        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;
+    }
+
+    // no masking, no errors
+    psVectorClipFitPolynomial2D (map->x, stats, NULL, 0, X, wt, x, y);
+    psVectorClipFitPolynomial2D (map->y, stats, NULL, 0, Y, wt, x, y);
+    psFree (x);
+    psFree (y);
+    psFree (X);
+    psFree (Y);
+    psFree (wt);
+    psFree (stats);
+
+    return (map);
+}
+
+
+
+/******************************************************************************
+pmAstromRotateObj(old, center, angle): rotate the focal-plane coordinates
+about the center coordinate angle specified in radians
+ ******************************************************************************/
+psArray *pmAstromRotateObj(
+    psArray *old,
+    psPlane center,
+    double angle)
+{
+    PS_ASSERT_PTR_NON_NULL(old, NULL);
+
+    double X, Y;
+    pmAstromObj *newObj;
+    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);
+}
+
+
+
+/******************************************************************************
+pmAstromObjFree(obj)
+ ******************************************************************************/
+static void pmAstromObjFree(pmAstromObj *obj)
+{
+    if (obj == NULL) {
+        return;
+    }
+
+    return;
+}
+
+
+/******************************************************************************
+pmAstromObjAlloc()
+ ******************************************************************************/
+pmAstromObj *pmAstromObjAlloc(void)
+{
+    pmAstromObj *obj = psAlloc (sizeof(pmAstromObj));
+    psMemSetDeallocator (obj, (psFreeFunc) pmAstromObjFree);
+
+    obj->pix.x = 0;
+    obj->pix.y = 0;
+
+    obj->FP.x = 0;
+    obj->FP.y = 0;
+
+    obj->TP.x = 0;
+    obj->TP.y = 0;
+
+    obj->sky.r = 0;
+    obj->sky.d = 0;
+
+    obj->Mag = 0;
+    obj->dMag = 0;
+
+    return (obj);
+}
+
+
+/******************************************************************************
+pmAstromObjCopy(old)
+ ******************************************************************************/
+pmAstromObj *pmAstromObjCopy(pmAstromObj *old)
+{
+    PS_ASSERT_PTR_NON_NULL(old, NULL);
+    pmAstromObj *obj = pmAstromObjAlloc();
+
+    obj[0] = old[0];
+
+    return(obj);
+}
+
+
+/******************************************************************************
+ ******************************************************************************/
+static void pmAstromMatchFree (pmAstromMatch *match)
+{
+    if (match == NULL)
+        return;
+    return;
+}
+
+
+/******************************************************************************
+ ******************************************************************************/
+pmAstromMatch *pmAstromMatchAlloc(
+    int i1,
+    int i2)
+{
+    pmAstromMatch *match = psAlloc (sizeof(pmAstromMatch));
+    psMemSetDeallocator(match, (psFreeFunc) pmAstromMatchFree);
+
+    match->i1 = i1;
+    match->i2 = i2;
+
+    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(
+    psArray *raw,
+    psArray *ref,
+    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
+
+    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;
+        fprintf (stderr, "minNpts: %d, max: %d\n", 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 emphasize 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(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];
+
+        // XXX EAM : This routine, and pmAstromGridMatch, need to handle failure cases better
+    }
+    return (stats);
+}
+
+
+
+/******************************************************************************
+pmAstromGridMatch(*raw, *ref, *config): match two star lists.
+ ******************************************************************************/
+
+pmAstromStats pmAstromGridMatch(
+    psArray *raw,
+    psArray *ref,
+    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;
+    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);
+        newStat.angle  = angle;
+        newStat.center = center;
+        if (newStat.minMetric < minStat.minMetric) {
+            minStat = newStat;
+        }
+        psFree (rot);
+    }
+    fprintf (stderr, "best: %f %f (%d pts, %f var, %f met)\n", newStat.offset.x, newStat.offset.y, newStat.nMatch, newStat.minVar, newStat.minMetric);
+    return (minStat);
+}
+
+
+/******************************************************************************
+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/rel11_ifa_pre0/psModules/src/astrom/pmAstrometryObjects.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmAstrometryObjects.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmAstrometryObjects.h	(revision 6961)
@@ -0,0 +1,390 @@
+/** @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.4 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-04-17 18:10:08 $
+*
+*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+*/
+
+#if !defined(PM_ASTROMETRYOBJECTS_H)
+#define PM_PM_ASTROMETRYOBJECTS_H_H
+
+# include <stdio.h>
+# include <strings.h>  // for strcasecmp
+# include <unistd.h>   // for unlink
+# include <pslib.h>
+# include <pmFPA.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;                         ///< A measurement of the brightness.
+    double dMag;                        ///< What is this?
+}
+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 i1;                             ///< What is this?
+    int i2;                             ///< 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(
+    psArray *st1,
+    psArray *st2,
+    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(
+    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(
+    psArray *st1,
+    psArray *st2,
+    psMetadata *config
+);
+
+
+/*
+ *
+ * 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(
+    psArray *st1,
+    psArray *st2,
+    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 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.
+ *
+ * XXX: No prototype code.
+ *
+ */
+psArray pmAstromMeasureGradients(
+    psArray *starlist1,
+    psArray *starlist2,
+    psArray *match,
+    psMetadata *config
+);
+
+
+
+/*
+ *
+ * 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;
+
+
+/*
+ *
+ * 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).
+ *
+ * XXX: No prototype code.
+ *
+ */
+psArray pmAstromFitDistortion(
+    pmFPA *fpa,
+    psArray *gradients,
+    psMetadata *config);
+
+
+
+
+
+/*******************************************************************************
+ The following functions and structs were in the prototype code, but not the
+ SDRS.
+ ******************************************************************************/
+/*
+ *
+ *
+ *
+ *
+ */
+
+
+/*
+ *
+ * Allocates a pmAstromObj struct.
+ *
+ */
+pmAstromObj *pmAstromObjAlloc (void);
+
+
+
+/*
+ *
+ * Copies a pmAstromObj struct.
+ *
+ */
+pmAstromObj *pmAstromObjCopy(
+    pmAstromObj *old
+);
+
+
+
+/*
+ *
+ *
+ *
+ */
+pmAstromMatch *pmAstromMatchAlloc(
+    int i1,
+    int i2
+);
+
+
+
+
+/*
+ *
+ *
+ *
+ */
+psPlaneTransform *pmAstromMatchFit(
+    psPlaneTransform *map,
+    psArray *st1,
+    psArray *st2,
+    psArray *match,
+    psMetadata *config
+);
+
+
+
+/*
+ *
+ *
+ *
+ */
+int pmAstromObjSortByFPX(
+    const void **a,
+    const void **b
+);
+
+
+
+/*
+ *
+ *
+ *
+ */
+int pmAstromObjSortByMag(
+    const void **a,
+    const void **b
+);
+
+#endif
+
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmChipMosaic.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmChipMosaic.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmChipMosaic.c	(revision 6961)
@@ -0,0 +1,367 @@
+#include <stdio.h>
+#include <assert.h>
+
+#include "pslib.h"
+#include "pmFPA.h"
+#include "pmChipMosaic.h"
+
+#define MEM_LEAKS 0
+
+// Compare a value with a maximum and minimum
+#define COMPARE(value,min,max) \
+if ((value) < (min)) { \
+    (min) = (value); \
+} \
+if ((value) > (max)) { \
+    (max) = (value); \
+}
+
+// Mosaic multiple images, with flips, binning and offsets
+psImage *p_pmImageMosaic(const psArray *source, // Images to splice in
+                         const psVector *xFlip, const psVector *yFlip, // Need to flip x and y?
+                         const psVector *xBinSource, const psVector *yBinSource, // Binning in x and 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);
+
+    // 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;
+    for (int i = 0; i < source->n; i++) {
+        psImage *image = source->data[i]; // The image of interest
+        if (!image) {
+            continue;
+        }
+
+        // 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(__func__, 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);
+    }
+
+    // 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(__func__, 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.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;
+        }
+        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; useful to test psImageOverlaySection if no other reason
+            psImageOverlaySection(mosaic, image, x0->data.S32[i], y0->data.S32[i], "+");
+        } else {
+            // We have to do the hard work ourself
+            for (int y = 0; y < image->numRows; y++) {
+                int yParity = yFlip->data.U8[i] ? -1 : 1;
+                float yTargetBase = (y0->data.S32[i] + yParity * yBinSource->data.S32[i] * y) / yBinTarget;
+                for (int x = 0; x < image->numCols; x++) {
+                    int xParity = xFlip->data.U8[i] ? -1 : 1;
+                    float xTargetBase = (x0->data.S32[i] + xParity * xBinSource->data.S32[i] * x) /
+                                        xBinTarget;
+
+                    // 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)                                                                    \
+                    for (int j = 0; j < yBinSource->data.S32[i]; j++) {                                      \
+                        int yTarget = (int)(yTargetBase + yParity * (float)j / (float)yBinTarget);           \
+                        for (int i = 0; i < xBinSource->data.S32[i]; i++) {                                  \
+                            int xTarget = (int)(xTargetBase + xParity * (float)i / (float)xBinTarget);       \
+                            mosaic->data.TYPE[yTarget][xTarget] += image->data.TYPE[y][x];                   \
+                        }                                                                                    \
+                    }
+
+                    switch (type) {
+                    case PS_TYPE_F32:
+                        FILL_IN(F32);
+                        break;
+                    case PS_TYPE_U8:
+                        FILL_IN(U8);
+                        break;
+                    default:
+                        psAbort(__func__, "Should never get here.\n");
+                    }
+
+                }
+            } // Iterating over input image
+        }
+    }
+
+    return mosaic;
+}
+
+
+// Set the concepts in the new cell, based on the values in the old one
+static bool cellConcepts(pmCell *target,// Target cell
+                         psArray *sources, // Source cells
+                         int xBin, int yBin // Binning
+                        )
+{
+    bool success = true;                // Result of setting everything
+    float gain       = 0.0;             // Gain
+    float readnoise  = 0.0;             // Read noise
+    float saturation = 0.0;             // Saturation level
+    float bad        = 0.0;             // 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 nCells = 0;                     // Number of cells;
+    for (int i = 0; i < sources->n; i++) {
+        pmCell *cell = sources->data[i];// The cell of interest
+        if (!cell) {
+            continue;
+        }
+
+        nCells++;
+        gain       += psMetadataLookupF32(NULL, cell->concepts, "CELL.GAIN");
+        readnoise  += psMetadataLookupF32(NULL, cell->concepts, "CELL.READNOISE");
+        saturation += psMetadataLookupF32(NULL, cell->concepts, "CELL.SATURATION");
+        bad        += psMetadataLookupF32(NULL, cell->concepts, "CELL.BAD");
+        exposure   += psMetadataLookupF32(NULL, cell->concepts, "CELL.EXPOSURE");
+        darktime   += psMetadataLookupF32(NULL, cell->concepts, "CELL.DARKTIME");
+        time       += psTimeToMJD(psMetadataLookupPtr(NULL, cell->concepts, "CELL.TIME"));
+        if (i == 0) {
+            timeSys = psMetadataLookupS32(NULL, cell->concepts, "CELL.TIMESYS");
+        } 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;
+        }
+    }
+    gain       /= (float)nCells;
+    readnoise  /= (float)nCells;
+    saturation /= (float)nCells;
+    bad        /= (float)nCells;
+    exposure   /= (float)nCells;
+    darktime   /= (float)nCells;
+    psTime *timePtr = psTimeFromMJD(time/(double)nCells);
+    timePtr = psTimeConvert(timePtr, timeSys);
+
+    // XXX *REALLY* need a generic "concept update" function that handles the type and comments transparently.
+    psMetadataAddF32(target->concepts, PS_LIST_TAIL, "CELL.GAIN", PS_META_REPLACE, "Gain (e/ADU)", gain);
+    psMetadataAddF32(target->concepts, PS_LIST_TAIL, "CELL.READNOISE", PS_META_REPLACE, "Read noise (e)", readnoise);
+    psMetadataAddF32(target->concepts, PS_LIST_TAIL, "CELL.SATURATION", PS_META_REPLACE, "Saturation level (ADU)", saturation);
+    psMetadataAddF32(target->concepts, PS_LIST_TAIL, "CELL.BAD", PS_META_REPLACE, "Bad level (ADU)", bad);
+    psMetadataAddF32(target->concepts, PS_LIST_TAIL, "CELL.EXPOSURE", PS_META_REPLACE, "Exposure time (sec)", exposure);
+    psMetadataAddF32(target->concepts, PS_LIST_TAIL, "CELL.DARKTIME", PS_META_REPLACE, "Time since last CCD flush (sec)", darktime);
+    psMetadataAddPtr(target->concepts, PS_LIST_TAIL, "CELL.TIME", PS_DATA_TIME | PS_META_REPLACE, "Time of observation", timePtr);
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.TIMESYS", PS_META_REPLACE, "Time system", timeSys);
+    psFree(timePtr);
+
+    // Now fill in the ones I know by other means
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.X0", PS_META_REPLACE, "Position of (0,0) on the chip", 0);
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.Y0", PS_META_REPLACE, "Position of (0,0) on the chip", 0);
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.XPARITY", PS_META_REPLACE, "Orientation in x compared to the rest of the FPA", 1);
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.YPARITY", PS_META_REPLACE, "Orientation in x compared to the rest of the FPA", 1);
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.XBIN", PS_META_REPLACE, "Binning in x", xBin);
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.YBIN", PS_META_REPLACE, "Binning in x", yBin);
+    psMetadataAddS32(target->concepts, PS_LIST_TAIL, "CELL.READDIR", PS_META_REPLACE, "Read direction (faked)", 1);
+    psRegion *trimsec = psMetadataLookupPtr(NULL, target->concepts, "CELL.TRIMSEC");
+    trimsec->x0 = trimsec->x1 = trimsec->y0 = trimsec->y1 = 0.0;
+
+    return success;
+}
+
+
+
+// Mosaic a chip together into a single image
+int pmChipMosaic(pmChip *chip,// Chip to mosaic
+                 int xBinChip, int yBinChip // Binning of mosaic image in x and y
+                )
+{
+
+    psArray *cells = chip->cells;       // The array of cells
+    psArray *images = psArrayAlloc(cells->n); // Array of images that will be mosaicked
+    psArray *weights = psArrayAlloc(cells->n); // Array of weight images to be mosaicked
+    psArray *masks = psArrayAlloc(cells->n); // Array of mask images to be mosaicked
+    psVector *x0 = psVectorAlloc(cells->n, PS_TYPE_S32); // Origin x coordinates
+    psVector *y0 = psVectorAlloc(cells->n, PS_TYPE_S32); // Origin y coordinates
+    psVector *xBin = psVectorAlloc(cells->n, PS_TYPE_S32); // Binning in x
+    psVector *yBin = psVectorAlloc(cells->n, PS_TYPE_S32); // Binning in y
+    psVector *xFlip = psVectorAlloc(cells->n, PS_TYPE_U8); // Flip in x?
+    psVector *yFlip = psVectorAlloc(cells->n, PS_TYPE_U8); // Flip in y?
+
+    // Set up the required inputs
+    psTrace(__func__, 1, "Mosaicking %d cells...\n", cells->n);
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // The cell of interest
+        if (!cell) {
+            continue;
+        }
+        x0->data.S32[i] = psMetadataLookupS32(NULL, cell->concepts, "CELL.X0");
+        y0->data.S32[i] = psMetadataLookupS32(NULL, cell->concepts, "CELL.Y0");
+        psTrace(__func__, 5, "Cell %d: x0=%d y0=%d\n", i, x0->data.S32[i], y0->data.S32[i]);
+        xBin->data.S32[i] = psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN");
+        yBin->data.S32[i] = psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN");
+        int xParity = psMetadataLookupS32(NULL, cell->concepts, "CELL.XPARITY");
+        int yParity = psMetadataLookupS32(NULL, cell->concepts, "CELL.YPARITY");
+        if (xParity == 1) {
+            xFlip->data.U8[i] = 0;
+        } else if (xParity == -1) {
+            xFlip->data.U8[i] = 1;
+        } else {
+            psLogMsg(__func__, PS_LOG_WARN, "The x parity of cell %d is not +/- 1 (it's %d) --- "
+                     "assuming +1.\n", i, xParity);
+            xFlip->data.U8[i] = 0;
+        }
+        if (yParity == 1) {
+            yFlip->data.U8[i] = 0;
+        } else if (yParity == -1) {
+            yFlip->data.U8[i] = 1;
+        } else {
+            psLogMsg(__func__, PS_LOG_WARN, "The y parity of cell %d is not +/- 1 (it's %d) --- "
+                     "assuming +1.\n", i, yParity);
+            yFlip->data.U8[i] = 0;
+        }
+
+        psArray *readouts = cell->readouts; // The array of readouts
+        if (readouts->n > 1) {
+            psLogMsg(__func__, PS_LOG_WARN, "Cell %d contains more than one readout --- only the first will "
+                     "be mosaicked.\n", i);
+        }
+        pmReadout *readout = readouts->data[0]; // The only readout we'll bother with
+
+        // The images to put into the mosaic
+        images->data[i]  = psMemIncrRefCounter(readout->image);
+        weights->data[i] = psMemIncrRefCounter(readout->weight);
+        masks->data[i]   = psMemIncrRefCounter(readout->mask);
+    }
+    // Mosaic the images together and we're done
+    psImage *image = p_pmImageMosaic(images, xFlip, yFlip, xBin, yBin, xBinChip, yBinChip, x0, y0);
+    psImage *weight = p_pmImageMosaic(weights, xFlip, yFlip, xBin, yBin, xBinChip, yBinChip, x0, y0);
+    psImage *mask = p_pmImageMosaic(masks, xFlip, yFlip, xBin, yBin, xBinChip, yBinChip, x0, y0);
+
+    // Clean up
+    psFree(x0);
+    psFree(y0);
+    psFree(xBin);
+    psFree(yBin);
+    psFree(xFlip);
+    psFree(yFlip);
+    psFree(images);
+    psFree(weights);
+    psFree(masks);
+    int nCells = cells->n;
+
+    // Fix up the HDU
+    if (chip->parent->hdu) {
+        psLogMsg(__func__, PS_LOG_WARN, "The original format has the entire FPA in a single extension.  "
+                 "The FPA hierarchy may be invalid following the pmChipMosaic.\n");
+    } else {
+        if (! chip->hdu) {
+            psString chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME");
+            chip->hdu = p_pmHDUAlloc(chipName);
+        }
+        p_pmHDU *hdu = chip->hdu;
+        psArrayElementsFree(hdu->images);
+        psArrayElementsFree(hdu->weights);
+        psArrayElementsFree(hdu->masks);
+        hdu->images  = psArrayRealloc(hdu->images,1);
+        hdu->weights = psArrayRealloc(hdu->weights, 1);
+        hdu->masks   = psArrayRealloc(hdu->masks, 1);
+        hdu->images->data[0]  = image;
+        hdu->weights->data[0] = weight;
+        hdu->masks->data[0]   = mask;
+        psMetadataAddS32(hdu->header, PS_LIST_TAIL, "NAXIS1", PS_META_REPLACE, "Number of columns", image->numCols);
+        psMetadataAddS32(hdu->header, PS_LIST_TAIL, "NAXIS2", PS_META_REPLACE, "Number of rows", image->numRows);
+    }
+
+    // Chop off all the component cells, and construct a new one
+    pmCell *newCell = pmCellAlloc(NULL, NULL, __func__); // New cell
+    cellConcepts(newCell, cells, xBinChip, yBinChip);
+    pmChipFreeCells(chip);
+    // Have to put in the new cell manually, since we didn't want to put it in before blowing the cells away.
+    newCell->parent = chip;
+    psArrayAdd(chip->cells, 1, newCell);
+    newCell->exists = true;
+    newCell->process = true;
+
+    // Now make a new readout to go in the new cell
+    pmReadout *newReadout = pmReadoutAlloc(newCell); // New readout
+    // Want the readouts to contain a subimage, but that subimage is the whole image.
+    // This preserves the relationship there was before, where freeing the parent frees the child.
+    psRegion entire = {0.0, 0.0, 0.0, 0.0};
+    newReadout->image = psMemIncrRefCounter(psImageSubset(image, entire));
+    newReadout->weight = psMemIncrRefCounter(psImageSubset(weight, entire));
+    newReadout->mask = psMemIncrRefCounter(psImageSubset(mask, entire));
+    // Drop references
+    psFree(newReadout);
+    psFree(newCell);
+
+    // Well, we've stuffed around with the camera configuration, so it's no longer valid...
+    #if 0
+
+    psFree(chip->parent->camera);
+    chip->parent->camera = NULL;
+    #endif
+
+    return nCells;
+}
+
+
+int pmFPAMosaicCells(pmFPA *fpa,        // FPA
+                     int xBinChip, int yBinChip // Binning of mosaic image in x and y
+                    )
+{
+    assert(fpa);
+
+    int numChips = 0;
+    psArray *chips = fpa->chips;        // Component chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // The chip of interest
+        if (! chip || ! chip->exists || ! chip->process) {
+            continue;
+        }
+
+        if (pmChipMosaic(chip, xBinChip, yBinChip) > 0) {
+            numChips++;
+        }
+    }
+
+    return numChips;
+
+}
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmChipMosaic.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmChipMosaic.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmChipMosaic.h	(revision 6961)
@@ -0,0 +1,26 @@
+#ifndef PM_CHIP_MOSAIC_H
+#define PM_CHIP_MOSAIC_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+// Mosaic multiple images, with flips, binning and offsets
+psImage *p_pmImageMosaic(const psArray *source, // Images to splice in
+                         const psVector *xFlip, const psVector *yFlip, // Need to flip x and y?
+                         const psVector *xBinSource, const psVector *yBinSource, // Binning in x and 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
+                        );
+
+// Mosaic a chip together into a single cell with single readout
+int pmChipMosaic(pmChip *chip,// Chip to mosaic
+                 int xBinChip, int yBinChip // Binning of mosaic image in x and y
+                );
+
+// Mosaic all the cells in all (valid) chips together (neglecting the overscans); return number of chips
+int pmFPAMosaicCells(pmFPA *fpa,        // FPA
+                     int xBinChip, int yBinChip // Binning of mosaic image in x and y
+                    );
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmConcepts.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmConcepts.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmConcepts.c	(revision 6961)
@@ -0,0 +1,574 @@
+// XXX *REALLY* need generic "concept update" and "concept read" functions that handles the type transparently
+
+#include <stdio.h>
+#include <assert.h>
+
+#include "pslib.h"
+#include "pmConcepts.h"
+#include "pmConceptsRead.h"
+#include "pmConceptsWrite.h"
+#include "pmConceptsStandard.h"
+#include "psAdditionals.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
+                      )
+{
+    assert(blank);
+    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
+                         )
+{
+    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(__func__, 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
+                        )
+{
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+
+    if (source & PM_CONCEPT_SOURCE_CAMERA && !(*read & PM_CONCEPT_SOURCE_CAMERA)) {
+        pmConceptsReadFromCamera(*specs, cell, target);
+        *read |= PM_CONCEPT_SOURCE_CAMERA;
+    }
+
+    if (source & PM_CONCEPT_SOURCE_DEFAULTS && !(*read & PM_CONCEPT_SOURCE_DEFAULTS)) {
+        pmConceptsReadFromDefaults(*specs, fpa, chip, cell, target);
+        *read |= PM_CONCEPT_SOURCE_DEFAULTS;
+    }
+
+    if (source & PM_CONCEPT_SOURCE_HEADER && !(*read & PM_CONCEPT_SOURCE_HEADER)) {
+        pmConceptsReadFromHeader(*specs, fpa, chip, cell, target);
+        *read |= PM_CONCEPT_SOURCE_HEADER;
+    }
+
+    if (source & PM_CONCEPT_SOURCE_DATABASE && !(*read & PM_CONCEPT_SOURCE_DATABASE)) {
+        pmConceptsReadFromDatabase(*specs, fpa, chip, cell, db, target);
+        *read |= PM_CONCEPT_SOURCE_DATABASE;
+    }
+
+    return true;
+}
+
+// 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
+                         )
+{
+    if (!conceptsInitialised) {
+        pmConceptsInit();
+    }
+
+    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_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
+                       )
+{
+    psTrace("psModule.concepts", 5, "Blanking FPA concepts: %x %x\n", conceptsFPA, fpa->concepts);
+    return conceptsBlank(&conceptsFPA, fpa->concepts);
+}
+
+
+// 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
+                       psDB *db         // Database handle
+                      )
+{
+    psTrace("psModule.concepts", 5, "Reading FPA concepts: %x %x\n", conceptsFPA, fpa->concepts);
+    return conceptsRead(&conceptsFPA, fpa, NULL, NULL, &fpa->conceptsRead, source, db, fpa->concepts);
+}
+
+// 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
+                        psDB *db        // Database handle
+                       )
+{
+    psTrace("psModule.concepts", 5, "Writing FPA concepts: %x %x\n", conceptsFPA, fpa->concepts);
+    return conceptsWrite(&conceptsFPA, fpa, NULL, NULL, source, db, fpa->concepts);
+}
+
+// Set the concepts for a given chip to blanks
+bool pmConceptsBlankChip(pmChip *chip // FPA for which to set blank concepts
+                        )
+{
+    psTrace("psModule.concepts", 5, "Blanking chip concepts: %x %x\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 propagate, // Propagate to higher levels as well?
+                        psDB *db        // Database handle
+                       )
+{
+    psTrace("psModule.concepts", 5, "Reading chip concepts: %x %x\n", conceptsChip, chip->concepts);
+    pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
+    return conceptsRead(&conceptsChip, fpa, chip, NULL, &chip->conceptsRead, source, db, chip->concepts) &&
+           ((propagate && conceptsRead(&conceptsFPA, fpa, chip, NULL, &fpa->conceptsRead, source, db,
+                                       fpa->concepts)) ||
+            !propagate);
+}
+
+// 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 propagate,// Propagate to higher levels as well?
+                         psDB *db        // Database handle
+                        )
+{
+    psTrace("psModule.concepts", 5, "Writing chip concepts: %x %x\n", conceptsChip, chip->concepts);
+    pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
+    return conceptsWrite(&conceptsChip, fpa, chip, NULL, source, db, chip->concepts) &&
+           ((propagate && conceptsWrite(&conceptsFPA, fpa, chip, NULL, source, db, fpa->concepts)) ||
+            !propagate);
+}
+
+// Set the concepts for a given chip to blanks
+bool pmConceptsBlankCell(pmCell *cell // Cell for which to set blank concepts
+                        )
+{
+    psTrace("psModule.concepts", 5, "Blanking cell concepts: %x %x\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 propagate,// Propagate to higher levels as well?
+                        psDB *db        // Database handle
+                       )
+{
+    psTrace("psModule.concepts", 5, "Reading cell concepts: %x %x\n", conceptsCell, cell->concepts);
+    pmChip *chip = cell->parent;        // Chip to which the cell belongs
+    pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
+    return conceptsRead(&conceptsCell, fpa, chip, cell, &cell->conceptsRead, source, db, cell->concepts) &&
+           ((propagate && conceptsRead(&conceptsChip, fpa, chip, cell, &chip->conceptsRead, source, db,
+                                       chip->concepts) &&
+             conceptsRead(&conceptsFPA, fpa, chip, cell, &fpa->conceptsRead, source, db, fpa->concepts)) ||
+            !propagate);
+}
+
+// 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 propagate,// Propagate to higher levels as well?
+                         psDB *db       // Database handle
+                        )
+{
+    psTrace("psModule.concepts", 5, "Writing cell concepts: %x %x\n", conceptsCell, cell->concepts);
+    pmChip *chip = cell->parent;        // Chip to which the cell belongs
+    pmFPA *fpa = chip->parent;          // FPA to which the chip belongs
+    return conceptsWrite(&conceptsCell, fpa, chip, cell, source, db, cell->concepts) &&
+           ((propagate && conceptsWrite(&conceptsChip, fpa, chip, cell, source, db, chip->concepts) &&
+             conceptsWrite(&conceptsFPA, fpa, chip, cell, source, db, fpa->concepts)) || !propagate);
+}
+
+
+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.AIRMASS
+        {
+            psMetadataItem *fpaAirmass = psMetadataItemAllocF32("FPA.AIRMASS", "Airmass at boresight", 0.0);
+            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", 0.0);
+            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)pmConceptParse_FPA_Coords,
+                              (pmConceptFormatFunc)pmConceptFormat_FPA_Coords, PM_FPA_LEVEL_FPA);
+            psFree(fpaRa);
+        }
+
+        // FPA.DEC
+        {
+            psMetadataItem *fpaDec = psMetadataItemAllocF64("FPA.DEC", "Declination of boresight", NAN);
+            pmConceptRegister(fpaDec, (pmConceptParseFunc)pmConceptParse_FPA_Coords,
+                              (pmConceptFormatFunc)pmConceptFormat_FPA_Coords, PM_FPA_LEVEL_FPA);
+            psFree(fpaDec);
+        }
+
+        // Done with FPA level concepts
+    }
+    if (! conceptsChip) {
+        conceptsChip = psMetadataAlloc();
+        init = true;
+        // There are no standard concepts at the chip level to be installed
+    }
+    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 FPA", 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 FPA", 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)pmConceptParse_CELL_TRIMSEC,
+                              (pmConceptFormatFunc)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)pmConceptParse_CELL_BIASSEC,
+                              (pmConceptFormatFunc)pmConceptFormat_CELL_BIASSEC, PM_FPA_LEVEL_CELL);
+            psFree(cellBiassec);
+        }
+
+        // CELL.XBIN
+        {
+            psMetadataItem *cellXbin = psMetadataItemAllocS32("CELL.XBIN", "Binning in x", 0);
+            pmConceptRegister(cellXbin, (pmConceptParseFunc)pmConceptParse_CELL_Binning,
+                              (pmConceptFormatFunc)pmConceptFormat_CELL_XBIN, PM_FPA_LEVEL_CELL);
+            psFree(cellXbin);
+        }
+
+        // CELL.YBIN
+        {
+            psMetadataItem *cellYbin = psMetadataItemAllocS32("CELL.YBIN", "Binning in y", 0);
+            pmConceptRegister(cellYbin, (pmConceptParseFunc)pmConceptParse_CELL_Binning,
+                              (pmConceptFormatFunc)pmConceptFormat_CELL_YBIN, PM_FPA_LEVEL_CELL);
+            psFree(cellYbin);
+        }
+
+        // CELL.TIMESYS
+        {
+            psMetadataItem *cellTimesys = psMetadataItemAllocS32("CELL.TIMESYS", "Time system", -1);
+            pmConceptRegister(cellTimesys, (pmConceptParseFunc)pmConceptParse_CELL_TIMESYS,
+                              (pmConceptFormatFunc)pmConceptFormat_CELL_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)pmConceptParse_CELL_TIME,
+                              (pmConceptFormatFunc)pmConceptFormat_CELL_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)pmConceptParse_CELL_Positions,
+                              (pmConceptFormatFunc)pmConceptFormat_CELL_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)pmConceptParse_CELL_Positions,
+                              (pmConceptFormatFunc)pmConceptFormat_CELL_Positions, PM_FPA_LEVEL_CELL);
+            psFree(cellY0);
+        }
+
+    }
+
+    return init;
+}
+
+void pmConceptsDone(void)
+{
+    psFree(conceptsFPA);
+    psFree(conceptsChip);
+    psFree(conceptsCell);
+}
+
+
+// Copy concepts from one FPA to another
+bool pmFPACopyConcepts(pmFPA *target,   // The target FPA
+                       pmFPA *source    // The target FPA
+                      )
+{
+    // 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 (%d) and source (%d) 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 (%d) and source (%d) 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/rel11_ifa_pre0/psModules/src/astrom/pmConcepts.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmConcepts.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmConcepts.h	(revision 6961)
@@ -0,0 +1,91 @@
+#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)(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+// Function to call to format a concept for writing
+typedef psMetadataItem* (*pmConceptFormatFunc)(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, 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_HEADER   = 0x04,  // Concept comes from FITS header
+    PM_CONCEPT_SOURCE_DATABASE = 0x08,  // Concept comes from database
+    PM_CONCEPT_SOURCE_ALL      = 0x0e   // All concepts
+} pmConceptSource;
+
+// 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
+                       psDB *db         // Database handle
+                      );
+bool pmConceptsWriteFPA(pmFPA *fpa,     // FPA for which to write concepts
+                        pmConceptSource source, // Source for concepts
+                        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 propagate, // Propagate to higher levels as well?
+                        psDB *db        // Database handle
+                       );
+bool pmConceptsWriteChip(pmChip *chip,  // Chip for which to write concepts
+                         pmConceptSource source, // Source for concepts
+                         bool propagate,// Propagate to higher levels as well?
+                         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 propagate, // Propagate to higher levels as well?
+                        psDB *db        // Database handle
+                       );
+bool pmConceptsWriteCell(pmCell *cell,  // FPA for which to write concepts
+                         pmConceptSource source, // Source for concepts
+                         bool propagate,// Propagate to higher levels as well?
+                         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/rel11_ifa_pre0/psModules/src/astrom/pmConceptsRead.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmConceptsRead.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmConceptsRead.c	(revision 6961)
@@ -0,0 +1,611 @@
+#include <stdio.h>
+
+#include "pslib.h"
+#include "psMetadataItemParse.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
+                                 )
+{
+    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
+                        )
+{
+    if (concept) {
+        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);
+        }
+
+        // Reformat so that everything's clean
+        if (strcmp(spec->blank->name, parsed->name) != 0 ||
+                strcmp(spec->blank->comment, parsed->comment) != 0) {
+            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, concept->type, spec->blank->comment,
+                                              parsed->data.V);
+            }
+            psFree(parsed);
+            parsed = cleaned;
+        }
+        psMetadataAddItem(target, parsed, PS_LIST_TAIL, PS_META_REPLACE);
+        psFree(parsed);     // Drop reference
+        return true;
+    }
+
+    return false;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmConceptsReadFromCamera(psMetadata *specs, // The concept specifications
+                              pmCell *cell,  // The cell
+                              psMetadata *target // Place into which to read the concepts
+                             )
+{
+    if (cell) {
+        pmHDU *hdu = pmHDUGetLowest(NULL, NULL, cell); // The HDU at the lowest level
+        if (! hdu) {
+            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
+        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
+            psMetadataItem *value = NULL; // The value of the concept
+            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) {
+                        value = conceptItem;
+                        conceptParse(spec, value, cameraFormat, target, NULL, NULL, cell);
+                    } 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
+                    conceptParse(spec, conceptItem, cameraFormat, target, NULL, NULL, cell);
+                }
+            }
+        }
+        psFree(specsIter);
+        return true;
+    }
+    return false;
+}
+
+
+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
+                               )
+{
+    pmHDU *hdu = pmHDUGetLowest(fpa, chip, 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) {
+        pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
+        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 *conceptItem = psMetadataLookup(defaults, name); // The concept, or NULL
+            conceptParse(spec, conceptItem, cameraFormat, target, fpa, chip, cell);
+        }
+        psFree(specsIter);
+        return true;
+    }
+    return false;
+}
+
+
+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
+                             )
+{
+    pmHDU *hdu = pmHDUGetLowest(fpa, chip, 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 *transSpec = psMetadataLookupMD(&mdok, cameraFormat, "TRANSLATION"); // The TRANSLATION spec
+    if (mdok && transSpec) {
+        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 = 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(hdu->header, conceptItem->data.V);
+                    }
+                    // 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, " ,;"); // List of keywords
+                    if (keys->n == 1) {
+                        // Only one key --- proceed as usual
+                        headerItem = psMetadataLookup(hdu->header, keywords);
+                    } 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(hdu->header, 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
+            conceptParse(spec, headerItem, cameraFormat, target, fpa, chip, cell);
+        }
+        psFree(specsIter);
+        return true;
+    }
+    return false;
+}
+
+
+// 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
+                               )
+{
+    #ifdef OMIT_PSDB
+    return false;
+    #else
+
+    pmHDU *hdu = pmHDUGetLowest(fpa, chip, 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 *dbSpec = psMetadataLookupMD(&mdok, cameraFormat, "DATABSE"); // The DATABASE spec
+    if (mdok && dbSpec) {
+        pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
+        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
+
+            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, ",;"); // List of column names
+                    psList *values = psStringSplit(givenPS, ",;"); // 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
+                        conceptParse(spec, conceptItem, cameraFormat, target, fpa, chip, cell);
+
+                    }
+                    psFree(cols);
+                    psFree(values);
+                }
+            } // Doing the "given"s.
+
+        } // Iterating through the concept specifications
+        psFree(specsIter);
+
+        return true;
+    }
+    return false;
+    #endif
+}
+
+
+
+
+#ifdef OLD
+
+psMetadataItem *pmConceptReadFromCamera(pmCell *cell, // The cell
+                                        const char *concept // Name of concept
+                                       )
+{
+    if (cell) {
+        psMetadata *camera = cell->config; // Camera configuration
+        psMetadataItem *item = psMetadataLookup(camera, concept);
+        return item;
+    }
+    return NULL;
+}
+
+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
+                                       )
+{
+    bool mdStatus = true;               // Status of MD lookup
+    psMetadata *translation = psMetadataLookupMD(&mdStatus, fpa->camera, "TRANSLATION"); // FITS translation
+    if (! mdStatus) {
+        psError(PS_ERR_IO, false, "Unable to find TRANSLATION in camera configuration.\n");
+        return NULL;
+    }
+
+    // Look for how to translate the concept into a FITS header name
+    const char *keyword = psMetadataLookupStr(&mdStatus, translation, concept);
+    if (mdStatus && strlen(keyword) > 0) {
+        // We have a FITS header to look up --- search each level
+        if (cell && cell->hdu) {
+            psMetadataItem *cellItem = psMetadataLookup(cell->hdu->header, keyword);
+            if (cellItem) {
+                // XXX: Need to clean up before returning
+                return cellItem;
+            }
+        }
+
+        if (chip && chip->hdu) {
+            psMetadataItem *chipItem = psMetadataLookup(chip->hdu->header, keyword);
+            if (chipItem) {
+                // XXX: Need to clean up before returning
+                return chipItem;
+            }
+        }
+
+        if (fpa->hdu) {
+            psMetadataItem *fpaItem = psMetadataLookup(fpa->hdu->header, keyword);
+            if (fpaItem) {
+                // XXX: Need to clean up before returning
+                return fpaItem;
+            }
+        }
+    }
+
+    // No header value
+    return NULL;
+}
+
+
+// Look for a default
+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
+                                        )
+{
+    bool mdOK = true;                   // Status of MD lookup
+    psMetadata *defaults = psMetadataLookupMD(&mdOK, fpa->camera, "DEFAULTS");
+    if (! mdOK) {
+        psError(PS_ERR_IO, false, "Unable to find DEFAULTS in camera configuration.\n");
+        return NULL;
+    }
+
+    psMetadataItem *defItem = psMetadataLookup(defaults, concept);
+    if (defItem) {
+        if (defItem->type == PS_DATA_METADATA) {
+            // A dependent default
+            psTrace(__func__, 7, "Evaluating dependent default....\n");
+            psMetadata *dependents = defItem->data.V; // The list of dependents
+            // Find out what it depends on
+            psString dependName = psStringCopy(concept);
+            psStringAppend(&dependName, ".DEPEND");
+            psString dependsOn = psMetadataLookupStr(&mdOK, defaults, dependName);
+            if (! mdOK) {
+                psError(PS_ERR_IO, false, "Unable to find %s in camera configuration for dependent default"
+                        " --- ignored\n", dependName);
+                // XXX: Need to clean up before returning
+                return NULL;
+            }
+            psFree(dependName);
+            // Find the value of the dependent concept
+            psMetadataItem *depItem = pmConceptReadFromHeader(fpa, chip, cell, dependsOn);
+            if (! depItem) {
+                psError(PS_ERR_IO, true, "Unable to find value for %s (required for %s)\n", dependsOn,
+                        concept);
+                return NULL;
+            }
+            if (depItem->type != PS_DATA_STRING) {
+                psError(PS_ERR_IO, true, "Value of %s is not of type string, as required for dependency"
+                        " --- ignored.\n", dependsOn);
+            }
+
+            defItem = psMetadataLookup(dependents, depItem->data.V);    // This is now what we were after
+        }
+    }
+
+    // XXX: Need to clean up before returning
+    return defItem;                     // defItem is either NULL or points to what was desired
+}
+
+
+// Look for a database lookup
+// XXX: Not tested
+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
+                                   )
+{
+    if (! db) {
+        // No database initialised
+        return NULL;
+    }
+
+    bool mdStatus = true;               // Status of MD lookup
+    psMetadata *database = psMetadataLookupMD(&mdStatus, fpa->camera, "DATABASE");
+    if (! mdStatus) {
+        // No error, because not everyone needs to use the DB
+        return NULL;
+    }
+
+    psMetadata *dbLookup = psMetadataLookupMD(&mdStatus, database, concept);
+    if (dbLookup) {
+        const char *tableName = psMetadataLookupStr(&mdStatus, dbLookup, "TABLE"); // Name of the table
+        // const char *colName = psMetadataLookupStr(&mdStatus, dbLookup, "COLUMN"); // Name of the column
+        const char *givenCols = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENDBCOL"); // Name of "where"
+        // columns
+        const char *givenPS = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENPS"); // Values for "where"
+        // columns
+
+        // Now, need to get the "given"s
+        if (strlen(givenCols) || strlen(givenPS)) {
+            psList *cols = psStringSplit(givenCols, ",;"); // List of column names
+            psList *values = psStringSplit(givenPS, ",;"); // 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", concept);
+            } 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 *name = 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", concept);
+                    } else {
+                        // Search for the value name
+                        psMetadataItem *item = pmConceptReadFromHeader(fpa, chip, cell, name);
+                        if (! item) {
+                            item = pmConceptReadFromDefault(fpa, chip, cell, name);
+                        }
+                        if (! item) {
+                            psLogMsg(__func__, PS_LOG_ERROR, "Unable to find the value name %s for DB "
+                                     " lookup on %s --- ignored.\n", name, concept);
+                        } 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(concept, item->type,
+                                                      item->comment, item->data.V);
+                            psMetadataAddItem(selection, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+                            psFree(newItem);
+                        }
+                    }
+                    psFree(name);
+                    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 *result = 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",
+                             concept);
+                } else {
+                    if (dbResult-> n > 1) {
+                        psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s --- "
+                                 " using the first one only.\n", concept);
+                    }
+                    result = (psMetadataItem*)dbResult->data[0];
+                }
+                // XXX: Need to clean up before returning
+                return result;
+            }
+            psFree(cols);
+            psFree(values);
+        }
+    } // Doing the "given"s.
+
+    psAbort(__func__, "Shouldn't ever get here.\n");
+    return NULL;
+}
+
+
+// Concept lookup
+psMetadataItem *pmConceptRead(pmFPA *fpa, // The FPA
+                              pmChip *chip,// The chip
+                              pmCell *cell, // The cell
+                              psDB *db, // DB handle
+                              const char *name // Concept name
+                             )
+{
+    // Try headers, database, defaults in order
+    psMetadataItem *item = pmConceptReadFromCamera(cell, name);
+    if (! item) {
+        item = pmConceptReadFromHeader(fpa, chip, cell, name);
+    }
+    if (! item) {
+        item = pmConceptReadFromDB(fpa, chip, cell, db, name);
+    }
+    if (! item) {
+        item = pmConceptReadFromDefault(fpa, chip, cell, name);
+    }
+    return item; // item is either NULL, or points to what was desired
+}
+
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmConceptsRead.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmConceptsRead.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmConceptsRead.h	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/astrom/pmConceptsStandard.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmConceptsStandard.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmConceptsStandard.c	(revision 6961)
@@ -0,0 +1,629 @@
+#include <stdio.h>
+#include <assert.h>
+
+#include "pslib.h"
+
+#include "pmFPA.h"
+#include "pmConceptsRead.h"
+#include "pmConceptsWrite.h"
+#include "pmConceptsStandard.h"
+
+
+#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 *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_IO, true, "Cannot interpret FPA.RA: %s\n", concept->data.V);
+                break;
+            }
+            coords = abs(big) + (float)medium/60.0 + small/3600.0;
+            if (big < 0)
+            {
+                coords *= -1.0;
+            }
+        }
+        break;
+    default:
+        psError(PS_ERR_IO, 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 *pmConceptFormat_FPA_Coords(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat)
+{
+    assert(concept);
+    assert(pattern);
+    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, 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);
+    }
+
+    // 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(pattern->name, pattern->comment, coordString);
+    psFree(coordString);
+
+    return coordItem;
+}
+
+
+psMetadataItem *pmConceptParse_CELL_TRIMSEC(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    assert(concept);
+    assert(cell);
+    assert(pattern);
+
+    psRegion *trimsec = psAlloc(sizeof(psRegion)); // Make space for a psRegion (usually passed by value)
+
+    if (concept->type != PS_DATA_STRING) {
+        psError(PS_ERR_IO, true, "CELL.TRIMSEC after read is not of type STR (%x)\n", concept->type);
+        *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+    } else {
+        *trimsec = psRegionFromString(concept->data.V);
+    }
+
+    psMetadataItem *item = psMetadataItemAllocPtr(pattern->name, PS_DATA_REGION, pattern->comment, trimsec);
+    psFree(trimsec);
+    return item;
+}
+
+psMetadataItem *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, " ;"); // 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_IO, 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 *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_IO, true, "Unable to parse string to get %s: %s\n", pattern->name, binString);
+            }
+        }
+        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_IO, 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 *pmConceptParse_CELL_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_IO, true, "Can't interpret CELL.TIMESYS --- assuming UTC.\n");
+    } 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_IO, true, "Can't interpret CELL.TIMESYS --- assuming UTC.\n");
+    }
+
+    return psMetadataItemAllocS32(pattern->name, pattern->comment, timeSys);
+}
+
+
+psMetadataItem *pmConceptParse_CELL_TIME(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    assert(concept);
+    assert(cameraFormat);
+    assert(cell);
+
+    // Need CELL.TIMESYS first
+    bool mdok = true;                  // Result of MD lookup
+    psTimeType timeSys = psMetadataLookupS32(&mdok, cell->concepts, "CELL.TIMESYS"); // The time system
+    if (!mdok) {
+        psLogMsg(__func__, PS_LOG_WARN, "Unable to find CELL.TIMESYS in concepts --- assuming UTC.\n");
+        timeSys = PS_TIME_UTC;
+    }
+
+    // Get format
+    psMetadata *formats = psMetadataLookupMD(&mdok, cameraFormat, "FORMATS");
+    if (!mdok || !formats) {
+        psError(PS_ERR_IO, false, "Unable to find FORMATS in camera configuration.\n");
+        return NULL;
+    }
+
+    psString timeFormat = psMetadataLookupStr(&mdok, formats, "CELL.TIME");
+    if (!mdok || strlen(timeFormat) == 0) {
+        psError(PS_ERR_IO, false, "Unable to find CELL.TIME in FORMATS.\n");
+        return NULL;
+    }
+
+    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_IO, 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", &day, &month, &year) != 3 &&
+                    sscanf(dateString, "%d/%d/%d", &day, &month, &year) != 3) {
+                psError(PS_ERR_IO, true, "Unable to read date: %s\n", dateString);
+                return NULL;
+            }
+            if (strstr(timeFormat, "BACKWARDS")) {
+                int temp = day;
+                day = year;
+                year = temp;
+            }
+            if (strstr(timeFormat, "PRE2000") || year < 2000) {
+                year += 2000;
+            }
+
+            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_IO, 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);
+            break;
+        }
+    case PS_DATA_STRING: {
+            psString timeString = concept->data.V;   // String with the time
+            if (strcasecmp(timeFormat, "ISO") == 0) {
+                // timeString contains an ISO time
+                time = psTimeFromISO(timeString, timeSys);
+            } else if (strcasecmp(timeFormat, "JD") == 0) {
+                double timeValue = strtod (timeString, NULL);
+                time = psTimeFromJD(timeValue);
+            } else if (strcasecmp(timeFormat, "MJD") == 0) {
+                double timeValue = strtod (timeString, NULL);
+                time = psTimeFromMJD(timeValue);
+            } else {
+                psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%s) --- trying "
+                        "ISO\n", timeString);
+                time = psTimeFromISO(timeString, timeSys);
+            } // Interpreting the time string
+            break;
+        }
+    case PS_TYPE_F32: {
+            double timeValue = (double)concept->data.F32;
+            if (strcasecmp(timeFormat, "JD") == 0) {
+                time = psTimeFromJD(timeValue);
+            } else if (strcasecmp(timeFormat, "MJD") == 0) {
+                time = psTimeFromMJD(timeValue);
+            } else {
+                psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%f) --- trying "
+                        "JD\n", timeValue);
+                time = psTimeFromJD(timeValue);
+            }
+            break;
+        }
+    case PS_TYPE_F64: {
+            double timeValue = (double)concept->data.F64;
+            if (strcasecmp(timeFormat, "JD") == 0) {
+                time = psTimeFromJD(timeValue);
+            } else if (strcasecmp(timeFormat, "MJD") == 0) {
+                time = psTimeFromMJD(timeValue);
+            } else {
+                psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%f) --- trying "
+                        "JD\n", timeValue);
+                time = psTimeFromJD(timeValue);
+            }
+            break;
+        }
+    default:
+        psError(PS_ERR_IO, true, "Unable to parse CELL.TIME.\n");
+        return NULL;
+    }
+
+    psMetadataItem *item = psMetadataItemAllocPtr(pattern->name, PS_DATA_TIME, pattern->comment, time);
+    psFree(time);
+    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 *pmConceptParse_CELL_Positions(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    assert(concept);
+    assert(cameraFormat);
+
+    int offset = 0;                     // Offset of cell (0,0) corner from the chip (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:
+        psError(PS_ERR_IO, 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 *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 *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 *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 *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 *pmConceptFormat_CELL_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 *pmConceptFormat_CELL_TIME(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    psTime *time = concept->data.V;     // The time
+    psMetadata *formats = psMetadataLookupMD(NULL, cameraFormat, "FORMATS");
+    psString format = psMetadataLookupStr(NULL, formats, "CELL.TIME");
+
+    if (strlen(format) == 0 || strcasecmp(format, "ISO") == 0) {
+        psString dateTimeString = psTimeToISO(time); // String representation
+        psMetadataItem *item = psMetadataItemAllocStr(concept->name, concept->comment, dateTimeString);
+        psFree(dateTimeString);
+        return item;
+    }
+    if (strstr(format, "SEPARATE")) {
+        // 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");
+        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 (strstr(format, "PRE2000")) {
+            psError(PS_ERR_IO, true, "Don't you realise it's the twenty-first century?\n");
+            return NULL;
+        }
+        if (strstr(format, "BACKWARDS")) {
+            psError(PS_ERR_IO, true, "You want it BACKWARDS?  Not right now, thanks.\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 (strcasecmp(format, "MJD") == 0) {
+        double mjd = psTimeToMJD(time);
+        return psMetadataItemAllocF64(concept->name, concept->comment, mjd);
+    }
+    if (strcasecmp(format, "JD") == 0) {
+        double jd = psTimeToMJD(time);
+        return psMetadataItemAllocF64("CELL.TIME", "JD of observation", jd);
+    }
+
+    psError(PS_ERR_IO, true, "Not sure how to format concept CELL.TIME\n");
+    return NULL;
+}
+
+psMetadataItem *pmConceptFormat_CELL_Positions(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell)
+{
+    assert(concept);
+    assert(cameraFormat);
+
+    if (concept->type != PS_TYPE_S32) {
+        psError(PS_ERR_IO, 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/rel11_ifa_pre0/psModules/src/astrom/pmConceptsStandard.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmConceptsStandard.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmConceptsStandard.h	(revision 6961)
@@ -0,0 +1,24 @@
+#ifndef PM_CONCEPTS_STANDARD_H
+#define PM_CONCEPTS_STANDARD_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+
+psMetadataItem *pmConceptParse_FPA_Coords(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptFormat_FPA_Coords(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat);
+psMetadataItem *pmConceptParse_CELL_TRIMSEC(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptParse_CELL_BIASSEC(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptParse_CELL_Binning(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptParse_CELL_TIMESYS(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptParse_CELL_TIME(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptParse_CELL_Positions(psMetadataItem *concept, psMetadataItem *pattern, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptFormat_CELL_TRIMSEC(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptFormat_CELL_BIASSEC(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptFormat_CELL_XBIN(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptFormat_CELL_YBIN(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptFormat_CELL_TIMESYS(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptFormat_CELL_TIME(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+psMetadataItem *pmConceptFormat_CELL_Positions(psMetadataItem *concept, psMetadata *cameraFormat, pmFPA *fpa, pmChip *chip, pmCell *cell);
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmConceptsWrite.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmConceptsWrite.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmConceptsWrite.c	(revision 6961)
@@ -0,0 +1,833 @@
+#include <stdio.h>
+#include <strings.h>
+#include "pslib.h"
+
+#include "pmFPA.h"
+#include "pmHDU.h"
+#include "pmHDUUtils.h"
+#include "pmConcepts.h"
+#include "pmConceptsRead.h"
+#include "psAdditionals.h"
+#include "psMetadataItemCompare.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, " ;");
+            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
+                                    )
+{
+    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
+                             )
+{
+    if (!hdu->header) {
+        return false;
+    }
+    switch (item->type) {
+    case PS_DATA_STRING:
+        return psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                item->data.V);
+    case PS_DATA_S32:
+        return psMetadataAddS32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                item->data.S32);
+    case PS_DATA_F32:
+        return psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, item->comment,
+                                item->data.F32);
+    case PS_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);
+            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
+                       )
+{
+    bool status = true;                 // Status of writing headers, to be returned
+    if (item->type == PS_DATA_LIST) {
+        psList *values = item->data.V;  // List of outputs
+        if (values->n == 0) {
+            // Nothing to write
+            return false;
+        }
+        psList *keys = psStringSplit(keywords, " ,;"); // List of keywords
+        if (keys->n != values->n) {
+            psLogMsg(__func__, PS_LOG_WARN, "Number of keywords (%d) does not match number of values (%d).\n",
+                     keys->n, values->n);
+        }
+        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
+        psMetadataItem *value = NULL;   // Value from iteration
+        while ((key = psListGetAndIncrement(keysIter)) && (value = psListGetAndIncrement(valuesIter))) {
+            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
+                            )
+{
+    if (cell) {
+        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(__func__, 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(__func__, 8, "Writing %s to header %s\n", name, cameraItem->data.V);
+                        writeHeader(hdu, cameraItem->data.V, formatted);
+                    } else if (strcasecmp(source, "VALUE") == 0) {
+                        psTrace(__func__, 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;
+    }
+    return false;
+}
+
+bool pmConceptsWriteToDefaults(psMetadata *specs, // The concept specifications
+                               pmFPA *fpa, // The FPA
+                               pmChip *chip, // The chip
+                               pmCell *cell, // The cell
+                               psMetadata *concepts // The concepts
+                              )
+{
+    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) {
+        pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
+        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 *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;
+    }
+    return false;
+}
+
+
+bool pmConceptsWriteToHeader(psMetadata *specs, // The concept specifications
+                             pmFPA *fpa, // The FPA
+                             pmChip *chip, // The chip
+                             pmCell *cell, // The cell
+                             psMetadata *concepts // The concepts
+                            )
+{
+    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) {
+        pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
+        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 *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;
+                }
+                psMetadataItem *conceptItem = psMetadataLookup(concepts, name); // The item from the concepts
+                psMetadataItem *formatted = conceptFormat(spec, conceptItem, cameraFormat, fpa, chip, cell);
+                if (!formatted) {
+                    continue;
+                }
+                psList *keywords = psStringSplit(headerItem->data.V, " ,;"); // List of header keywords
+                if (formatted->type == PS_DATA_LIST) {
+                    psList *values = formatted->data.V; // The values for the headers
+                    if (values->n != keywords->n) {
+                        psLogMsg(__func__, PS_LOG_WARN, "Number of headers specified does not match number "
+                                 "of values for concept %s.\n", name);
+                    }
+                    psListIterator *valuesIter = psListIteratorAlloc(values, PS_LIST_HEAD, false); // Iterator
+                    psListIterator *keywordsIter = psListIteratorAlloc(keywords, PS_LIST_HEAD, false);
+                    psMetadataItem *valuesItem = NULL; // Item from list
+                    while ((valuesItem = psListGetAndIncrement(valuesIter))) {
+                        psString keyword = psListGetAndIncrement(keywordsIter); // Keyword from the list
+                        if (strlen(keyword) > 0) {
+                            writeHeader(hdu, keyword, formatted);
+                        }
+                    }
+                    psFree(valuesIter);
+                    psFree(keywordsIter);
+                } else {
+                    psString keyword = psListGet(keywords, PS_LIST_HEAD); // The keyword
+                    writeHeader(hdu, keyword, formatted);
+                }
+                psFree(formatted);
+                psFree(keywords);
+            }
+        }
+        psFree(specsIter);
+        return true;
+    }
+    return false;
+}
+
+// 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
+                              )
+{
+    #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) {
+        pmHDU *hdu = pmHDUGetLowest(fpa, chip, cell); // The HDU at the lowest level
+        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 *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, ",;"); // List of column names
+                    psList *values = psStringSplit(givenPS, ",;"); // 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;
+    }
+    return false;
+    #endif
+}
+
+
+
+
+#if 0
+
+// Well, not really "write", but check to make sure it's there and matches
+bool pmConceptWriteToCamera(pmCell *cell, // The cell
+                            psMetadataItem *concept // Concept
+                           )
+{
+    if (! cell->config) {
+        return false;
+    }
+    if (cell) {
+        psMetadataItem *item = psMetadataLookup(cell->config, concept->name); // Info we want
+        return compareConcepts(item, concept);
+    }
+
+    return false;
+}
+
+// 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
+                           )
+{
+    bool mdok = true;                   // Status of MD lookup
+    bool status = false;                // Status of setting header
+    if (! fpa->camera) {
+        return false;
+    }
+    psMetadata *translation = psMetadataLookupMD(&mdok, fpa->camera, "TRANSLATION"); // FITS translation
+    if (! mdok) {
+        psError(PS_ERR_IO, false, "Unable to find TRANSLATION in camera configuration.\n");
+        return false;
+    }
+
+    // Look for how to translate the concept into a FITS header name
+    const char *keyword = psMetadataLookupStr(&mdok, translation, concept->name);
+    if (mdok && strlen(keyword) > 0) {
+        psTrace(__func__, 6, "It's in keyword %s\n", keyword);
+        psMetadataItem *headerItem = NULL; // Item to add to header
+        // XXX: Need to expand range of types
+        switch (concept->type) {
+        case PS_DATA_STRING:
+            headerItem = psMetadataItemAllocStr(keyword, concept->comment, concept->data.V);
+            break;
+        case PS_DATA_S32:
+            headerItem = psMetadataItemAllocS32(keyword, concept->comment, concept->data.S32);
+            break;
+        case PS_DATA_F32:
+            headerItem = psMetadataItemAllocF32(keyword, concept->comment, concept->data.F32);
+            break;
+        case PS_DATA_F64:
+            headerItem = psMetadataItemAllocF64(keyword, concept->comment, concept->data.F64);
+            break;
+        default:
+            headerItem = psMetadataItemAlloc(keyword, concept->type, concept->comment,
+                                             concept->data.V); // Item for the header
+        }
+
+        // We have a FITS header to look up --- search each level
+        if (cell && cell->hdu) {
+            psTrace(__func__, 7, "Adding to the cell level header...\n");
+            psMetadataAddItem(cell->hdu->header, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
+            status = true;
+        } else if (chip && chip->hdu) {
+            psTrace(__func__, 7, "Adding to the chip level header...\n");
+            psMetadataAddItem(chip->hdu->header, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
+            status = true;
+        } else if (fpa->hdu) {
+            psTrace(__func__, 7, "Adding to the FPA level header...\n");
+            psMetadataAddItem(fpa->hdu->header, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
+            status = true;
+        }
+        psFree(headerItem);
+    }
+
+    // No header value
+    return status;
+}
+
+
+// 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 mdOK = true;                   // Status of MD lookup
+    if (! fpa->camera) {
+        return false;
+    }
+    psMetadata *defaults = psMetadataLookupMD(&mdOK, fpa->camera, "DEFAULTS");
+    if (! mdOK || ! defaults) {
+        psError(PS_ERR_IO, false, "Unable to find DEFAULTS in camera configuration.\n");
+        return false;
+    }
+
+    psMetadataItem *defItem = psMetadataLookup(defaults, concept->name);
+    bool status = false;                // Result of checking the database
+    if (defItem) {
+        if (defItem->type == PS_DATA_METADATA) {
+            // A dependent default
+            psTrace(__func__, 7, "Evaluating dependent default....\n");
+            psMetadata *dependents = defItem->data.V; // The list of dependents
+            // Find out what it depends on
+            psString dependName = psStringCopy(concept->name);
+            psStringAppend(&dependName, ".DEPEND");
+            psString dependsOn = psMetadataLookupStr(&mdOK, defaults, dependName);
+            if (! mdOK) {
+                psError(PS_ERR_IO, false, "Unable to find %s in camera configuration for dependent default"
+                        " --- ignored\n", dependName);
+                // XXX: Need to clean up before returning
+                return false;
+            }
+            psFree(dependName);
+            // Find the value of the dependent concept
+            psMetadataItem *depItem = pmConceptReadFromHeader(fpa, chip, cell, dependsOn);
+            if (! depItem) {
+                psError(PS_ERR_IO, true, "Unable to find value for %s (required for %s)\n", dependsOn,
+                        concept->name);
+                return false;
+            }
+            if (depItem->type != PS_DATA_STRING) {
+                psError(PS_ERR_IO, true, "Value of %s is not of type string, as required for dependency"
+                        " --- ignored.\n", dependsOn);
+            }
+
+            defItem = psMetadataLookup(dependents, depItem->data.V); // This is now what we were after
+        }
+
+        status = compareConcepts(defItem, concept);
+        if (! status) {
+            psError(PS_ERR_IO, true, "Concept %s is specified by default in the camera configuration, "
+                    "but doesn't match the actual value.\n", concept->name);
+        }
+    }
+
+    // XXX: Need to clean up before returning
+    return status;
+}
+
+
+// XXX: Not tested at all
+// XXX I WOULD NOT TRUST THIS FUNCTION IN THE SLIGHTEST YET! --- PAP
+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
+                       )
+{
+    if (! db) {
+        // No database initialised
+        return false;
+    }
+
+    bool mdStatus = true;               // Status of MD lookup
+    if (! fpa->camera) {
+        return false;
+    }
+    psMetadata *database = psMetadataLookupMD(&mdStatus, fpa->camera, "DATABASE");
+    if (! mdStatus) {
+        // No error, because not everyone needs to use the DB
+        return NULL;
+    }
+
+    psMetadata *dbLookup = psMetadataLookupMD(&mdStatus, database, concept->name);
+    if (dbLookup) {
+        const char *tableName = psMetadataLookupStr(&mdStatus, dbLookup, "TABLE"); // Name of the table
+        //        const char *colName = psMetadataLookupStr(&mdStatus, dbLookup, "COLUMN"); // Name of the column
+        const char *givenCols = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENDBCOL"); // Name of "where"
+        // columns
+        const char *givenPS = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENPS"); // Values for "where"
+        // columns
+
+        // Now, need to get the "given"s
+        if (strlen(givenCols) || strlen(givenPS)) {
+            psList *cols = psStringSplit(givenCols, ",;"); // List of column names
+            psList *values = psStringSplit(givenPS, ",;"); // 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", concept);
+            } 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 *name = 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", concept);
+                    } else {
+                        // Search for the value name
+                        psMetadataItem *item = pmConceptReadFromHeader(fpa, chip, cell, name);
+                        if (! item) {
+                            item = pmConceptReadFromDefault(fpa, chip, cell, name);
+                        }
+                        if (! item) {
+                            psLogMsg(__func__, PS_LOG_ERROR, "Unable to find the value name %s for DB "
+                                     " lookup on %s --- ignored.\n", name, concept);
+                        } 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(concept->name, item->type,
+                                                      item->comment, item->data.V);
+                            psMetadataAddItem(selection, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+                            psFree(newItem);
+                        }
+                    }
+                    psFree(name);
+                    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",
+                             concept->name);
+                    return false;
+                } else {
+                    if (dbResult->n > 1) {
+                        psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s --- "
+                                 " ignored.\n", concept->name);
+                    }
+                    // Update the DB
+                    psMetadata *update = psMetadataAlloc();
+                    psMetadataAddItem(update, concept, PS_LIST_HEAD, 0);
+                    psDBUpdateRows(db, tableName, selection, update);
+                    psFree(update);
+                    return true;
+                }
+            }
+            psFree(cols);
+            psFree(values);
+        }
+    } // Doing the "given"s.
+
+    psAbort(__func__, "Shouldn't ever get here?\n");
+    return false;
+}
+
+
+// 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
+                       )
+{
+    if (! fpa->camera) {
+        return false;
+    }
+
+    // Try headers, database, defaults in order
+    psTrace(__func__, 3, "Trying to set concept %s...\n", concept->name);
+    bool status = pmConceptWriteToCamera(cell, concept); // Status for return
+    if (! status) {
+        psTrace(__func__, 5, "Trying header....\n");
+        status = pmConceptWriteToHeader(fpa, chip, cell, concept);
+    }
+    if (! status) {
+        psTrace(__func__, 5, "Trying database....\n");
+        status = pmConceptWriteToDB(fpa, chip, cell, db, concept);
+    }
+    if (! status) {
+        psTrace(__func__, 5, "Checking defaults....\n");
+        status = pmConceptWriteToDefault(fpa, chip, cell, concept);
+    }
+
+    if (! status) {
+        psError(PS_ERR_IO, true, "Unable to set %s (%s).\n", concept->name, concept->comment);
+    }
+
+    return status;
+}
+
+
+// Concept write
+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
+                   )
+{
+    psMetadataItem *concept = psMetadataLookup(concepts, name);
+    if (! concept) {
+        psError(PS_ERR_IO, true, "No such concept as %s\n", name);
+        return false;
+    }
+    return pmConceptWriteItem(fpa, chip, cell, db, concept);
+}
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmConceptsWrite.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmConceptsWrite.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmConceptsWrite.h	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/astrom/pmFPA.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPA.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPA.c	(revision 6961)
@@ -0,0 +1,653 @@
+/** @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.4 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-04-20 22:30:33 $
+*
+*  Copyright 2004-2005 Maui High Performance Computing Center, University of Hawaii
+*/
+
+/******************************************************************************/
+/*  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"
+
+/******************************************************************************
+ *****************************************************************************/
+#define PS_FREE_HIERARCHY 1
+#define PARENT_LINKS 0
+
+static void readoutFree(pmReadout *readout)
+{
+    psTrace(__func__, 9, "Removing readout %lx from cell %lx...\n", (size_t) readout, (size_t) readout->parent);
+    if (readout->parent) {
+        psArray *readouts = readout->parent->readouts;
+        for (int i = 0; i < readouts->n; i++) {
+            if (readouts->data[i] == readout) {
+                //                    pmReadout *tmpReadout = readouts->data[i];
+                readouts->data[i] = NULL;
+                #if PARENT_LINKS
+
+                psFree(tmpReadout);
+                #endif
+
+                break;
+            }
+        }
+    }
+    psTrace(__func__, 9, "Freeing readout %lx\n", (size_t) readout);
+
+    #if PARENT_LINKS
+
+    psFree(readout->parent);
+    #endif
+
+    psFree(readout->image);
+    psFree(readout->mask);
+    psFree(readout->weight);
+    psFree(readout->analysis);
+    psFree(readout->bias);
+}
+
+static void cellFree(pmCell *cell)
+{
+    psTrace(__func__, 9, "Removing cell %lx from chip %lx...\n", (size_t)cell, (size_t)cell->parent);
+    if (cell->parent) {
+        psArray *cells = cell->parent->cells;
+        for (int i = 0; i < cells->n; i++) {
+            if (cells->data[i] == cell) {
+                //                    pmCell *tmpCell = cells->data[i];
+                cells->data[i] = NULL;
+                #if PARENT_LINKS
+
+                psFree(tmpCell);
+                #endif
+
+                break;
+            }
+        }
+    }
+    psTrace(__func__, 9, "Freeing cell %lx\n", (size_t)cell);
+
+    pmCellFreeReadouts(cell);
+    psFree(cell->readouts);
+    #if PARENT_LINKS
+
+    psFree(cell->parent);
+    #endif
+
+    psFree(cell->concepts);
+    psFree(cell->analysis);
+    psFree(cell->config);
+    psFree(cell->hdu);
+}
+
+static void chipFree(pmChip* chip)
+{
+    psTrace(__func__, 9, "Removing chip %lx from fpa %lx...\n", (size_t)chip, (size_t)chip->parent);
+    if (chip->parent) {
+        psArray *chips = chip->parent->chips;
+        for (int i = 0; i < chips->n; i++) {
+            if (chips->data[i] == chip) {
+                //                    pmChip *tmpChip = chips->data[i];
+                chips->data[i] = NULL;
+                #if PARENT_LINKS
+
+                psFree(tmpChip);
+                #endif
+
+                break;
+            }
+        }
+    }
+    psTrace(__func__, 9, "Freeing chip %lx\n", (size_t)chip);
+
+    pmChipFreeCells(chip);
+    psFree(chip->cells);
+    #if PARENT_LINKS
+
+    psFree(chip->parent);
+    #endif
+
+    #if 0
+
+    psFree(chip->toFPA);
+    psFree(chip->fromFPA);
+    #endif
+
+    psFree(chip->concepts);
+    psFree(chip->analysis);
+    psFree(chip->hdu);
+}
+
+
+static void FPAFree(pmFPA *fpa)
+{
+    psTrace(__func__, 9, "Freeing fpa %lx\n", (size_t)fpa);
+    #if 0
+
+    psFree(fpa->fromTangentPlane);
+    psFree(fpa->toTangentPlane);
+    psFree(fpa->projection);
+    #endif
+
+    psFree(fpa->concepts);
+    psFree(fpa->analysis);
+    psFree(fpa->camera);
+    //
+    // Set the parent to NULL in all fpa->chips before psFree(fpa->chips)
+    // in order to avoid memory reference counter problems.
+    //
+    psArray *chips = fpa->chips;
+    for (psS32 i = 0 ; i < chips->n ; i++) {
+        pmChip *tmpChip = chips->data[i];
+        if (! tmpChip) {
+            continue;
+        }
+        chips->data[i] = NULL;
+        tmpChip->parent = NULL;
+        if (PS_FREE_HIERARCHY == 1) {
+            psFree(tmpChip);
+        }
+    }
+    psFree(fpa->chips);
+    psFree(fpa->hdu);
+}
+
+void pmCellFreeReadouts(pmCell *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;
+        }
+        readouts->data[i] = NULL;
+        tmpReadout->parent = NULL;
+        psTrace(__func__, 9, "Will now free readout %lx...\n", (size_t)tmpReadout);
+        psFree(tmpReadout); // Drop the readout
+        #if PARENT_LINKS
+
+        psFree(cell); // Decrement reference counter on cell, since readout->parent isn't held any more
+        #endif
+
+    }
+    cell->readouts = psArrayRealloc(cell->readouts, 0);
+    cell->readouts->n = 0;
+}
+
+
+void pmChipFreeCells(pmChip *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;
+        }
+        cells->data[i] = NULL;
+        tmpCell->parent = NULL;
+        pmCellFreeReadouts(tmpCell);// Drop all readouts the cell holds
+        psFree(tmpCell);            // Drop the cell
+        #if PARENT_LINKS
+
+        psFree(chip); // Decrement reference counter on chip, since cell->parent isn't held any more
+        #endif
+
+    }
+    chip->cells = psArrayRealloc(chip->cells, 0);
+    chip->cells->n = 0;
+}
+
+
+pmReadout *pmReadoutAlloc(pmCell *cell)
+{
+    pmReadout *tmpReadout = (pmReadout *) psAlloc(sizeof(pmReadout));
+    tmpReadout->row0 = 0;
+    tmpReadout->col0 = 0;
+
+    tmpReadout->image = NULL;
+    tmpReadout->mask = NULL;
+    tmpReadout->weight = NULL;
+    tmpReadout->bias = psListAlloc(NULL);
+    tmpReadout->analysis = psMetadataAlloc();
+    #if PARENT_LINKS
+
+    tmpReadout->parent = psMemIncrRefCounter(cell);
+    #else
+
+    tmpReadout->parent = cell;
+    #endif
+
+    if (cell != NULL) {
+        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
+
+    psMemSetDeallocator(tmpReadout, (psFreeFunc) readoutFree);
+    return(tmpReadout);
+}
+
+// XXX: Verify these default values for row0, col0.
+// PAP: These values may disappear in the future in favour of values in the "concepts"?
+pmCell *pmCellAlloc(
+    pmChip *chip,
+    const char *name)
+{
+    pmCell *tmpCell = (pmCell *) psAlloc(sizeof(pmCell));
+
+    tmpCell->concepts = psMetadataAlloc();
+    tmpCell->conceptsRead = PM_CONCEPT_SOURCE_NONE;
+    psBool rc = psMetadataAddStr(tmpCell->concepts, PS_LIST_HEAD, "CELL.NAME", 0, NULL, name);
+    if (rc == false) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CELL.NAME to metadata.\n");
+    }
+    tmpCell->config = NULL;
+    tmpCell->analysis = psMetadataAlloc();
+    tmpCell->readouts = psArrayAlloc(0);
+    #if PARENT_LINKS
+
+    tmpCell->parent = psMemIncrRefCounter(chip);
+    #else
+
+    tmpCell->parent = chip;
+    #endif
+
+    if (chip != NULL) {
+        chip->cells = psArrayAdd(chip->cells, 1, (psPtr) tmpCell);
+    }
+    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->hdu = NULL;
+
+    pmConceptsBlankCell(tmpCell);
+
+    psMemSetDeallocator(tmpCell, (psFreeFunc) cellFree);
+    return(tmpCell);
+}
+
+// XXX: Verify these default values for row0, col0.
+// PAP: row0, col0 may disappear in the future in favour of storing values in the "concepts".
+pmChip *pmChipAlloc(
+    pmFPA *fpa,
+    const char *name)
+{
+    pmChip *tmpChip = (pmChip *) psAlloc(sizeof(pmChip));
+
+    #if 0
+
+    tmpChip->col0 = 0;
+    tmpChip->row0 = 0;
+    tmpChip->toFPA = NULL;
+    tmpChip->fromFPA = NULL;
+    #endif
+
+    tmpChip->concepts = psMetadataAlloc();
+    tmpChip->conceptsRead = PM_CONCEPT_SOURCE_NONE;
+    psBool rc = psMetadataAddStr(tmpChip->concepts, PS_LIST_HEAD, "CHIP.NAME", 0, NULL, name);
+    if (rc == false) {
+        psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not add CHIP.NAME to metadata.\n");
+    }
+    tmpChip->analysis = psMetadataAlloc();
+    tmpChip->cells = psArrayAlloc(0);
+    #if PARENT_LINKS
+
+    tmpChip->parent = psMemIncrRefCounter(fpa);
+    #else
+
+    tmpChip->parent = fpa;
+    #endif
+
+    if (fpa != NULL) {
+        fpa->chips = psArrayAdd(fpa->chips, 1, (psPtr) tmpChip);
+    }
+    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->hdu = NULL;
+
+    pmConceptsBlankChip(tmpChip);
+
+    psMemSetDeallocator(tmpChip, (psFreeFunc) chipFree);
+    return(tmpChip);
+}
+
+pmFPA *pmFPAAlloc(const psMetadata *camera)
+{
+    pmFPA *tmpFPA = (pmFPA *) psAlloc(sizeof(pmFPA));
+
+    #if 0
+
+    tmpFPA->fromTangentPlane = NULL;
+    tmpFPA->toTangentPlane = NULL;
+    tmpFPA->projection = NULL;
+    #endif
+
+    tmpFPA->concepts = psMetadataAlloc();
+    tmpFPA->conceptsRead = PM_CONCEPT_SOURCE_NONE;
+    tmpFPA->analysis = NULL;
+    tmpFPA->camera = psMemIncrRefCounter((psPtr)camera);
+    tmpFPA->chips = psArrayAlloc(0);
+    tmpFPA->hdu = NULL;
+
+    pmConceptsBlankFPA(tmpFPA);
+
+    psMemSetDeallocator(tmpFPA, (psFreeFunc) FPAFree);
+    return(tmpFPA);
+}
+
+static psBool cellCheckParents(pmCell *cell)
+{
+    if (cell == NULL) {
+        return(true);
+    }
+    psBool flag = true;
+
+    for (psS32 i = 0 ; i < cell->readouts->n ; i++) {
+        pmReadout *tmpReadout = (pmReadout *) cell->readouts->data[i];
+        PS_ASSERT_PTR_NON_NULL(tmpReadout, false);
+        if (tmpReadout->parent != cell) {
+            tmpReadout->parent = cell;
+            flag = false;
+        }
+    }
+    return(flag);
+}
+
+static psBool chipCheckParents(pmChip *chip)
+{
+    if (chip == NULL) {
+        return(true);
+    }
+    psBool flag = true;
+
+    for (psS32 i = 0 ; i < chip->cells->n ; i++) {
+        pmCell *tmpCell = (pmCell *) chip->cells->data[i];
+        PS_ASSERT_PTR_NON_NULL(tmpCell, false);
+        if (tmpCell->parent != chip) {
+            tmpCell->parent = chip;
+            flag = false;
+        }
+
+        flag &= cellCheckParents(tmpCell);
+    }
+    return(flag);
+}
+
+psBool pmFPACheckParents(pmFPA *fpa)
+{
+    if (fpa == NULL) {
+        return(true);
+    }
+    psBool flag = true;
+
+    for (psS32 i = 0 ; i < fpa->chips->n ; i++) {
+        pmChip *tmpChip = (pmChip *) fpa->chips->data[i];
+        PS_ASSERT_PTR_NON_NULL(tmpChip, false);
+        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)
+{
+
+    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)
+{
+
+    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)
+{
+
+    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;
+}
+
+/** functions to turn on/off the data_exists flag **/
+bool pmFPASetDataStatus (pmFPA *fpa, bool status)
+{
+
+    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)
+{
+
+    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)
+{
+
+    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)
+{
+    assert(chip);
+
+    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, false);
+
+    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
+                     )
+{
+    assert(chip);
+
+    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;
+}
+
+
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPA.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPA.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPA.h	(revision 6961)
@@ -0,0 +1,283 @@
+/** @file  pmFPA.h
+*
+*  @brief This file defines the basic types the focal plane hierarchy.
+*
+*  @ingroup AstroImage
+*
+*  @author GLG, MHPCC
+*
+*  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-04-20 22:30:33 $
+*
+*  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"
+
+/// @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 0
+    // 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 0
+    // 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
+}
+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);
+
+/** 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 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
+                     );
+
+
+
+#endif // #ifndef PM_FPA_H
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAAstrometry.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAAstrometry.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAAstrometry.c	(revision 6961)
@@ -0,0 +1,512 @@
+#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/rel11_ifa_pre0/psModules/src/astrom/pmFPAAstrometry.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAAstrometry.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAAstrometry.h	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/astrom/pmFPAConceptsGet.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAConceptsGet.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAConceptsGet.c	(revision 6961)
@@ -0,0 +1,1197 @@
+#include <stdio.h>
+#include <strings.h>
+#include <assert.h>
+#include "pslib.h"
+
+#include "pmAstrometry.h"
+#include "pmFPAConceptsGet.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Private functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+psMetadataItem *p_pmFPAConceptGetFromCamera(pmCell *cell, // The cell
+        const char *concept // Name of concept
+                                           )
+{
+    if (cell) {
+        psMetadata *camera = cell->camera;      // Camera data
+        psMetadataItem *item = psMetadataLookup(camera, concept);
+        return item;
+    }
+    return NULL;
+}
+
+
+psMetadataItem *p_pmFPAConceptGetFromHeader(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
+                                           )
+{
+    bool mdStatus = true;               // Status of MD lookup
+    psMetadata *translation = psMetadataLookupMD(&mdStatus, fpa->camera, "TRANSLATION"); // FITS translation
+    if (! mdStatus) {
+        psError(PS_ERR_IO, false, "Unable to find TRANSLATION in camera configuration.\n");
+        return NULL;
+    }
+
+    // Look for how to translate the concept into a FITS header name
+    const char *keyword = psMetadataLookupStr(&mdStatus, translation, concept);
+    if (mdStatus && strlen(keyword) > 0) {
+        // We have a FITS header to look up --- search each level
+        if (cell && cell->hdu) {
+            psMetadataItem *cellItem = psMetadataLookup(cell->hdu->header, keyword);
+            if (cellItem) {
+                // XXX: Need to clean up before returning
+                return cellItem;
+            }
+        }
+
+        if (chip && chip->hdu) {
+            psMetadataItem *chipItem = psMetadataLookup(chip->hdu->header, keyword);
+            if (chipItem) {
+                // XXX: Need to clean up before returning
+                return chipItem;
+            }
+        }
+
+        if (fpa->hdu) {
+            psMetadataItem *fpaItem = psMetadataLookup(fpa->hdu->header, keyword);
+            if (fpaItem) {
+                // XXX: Need to clean up before returning
+                return fpaItem;
+            }
+        }
+
+        if (fpa->phu) {
+            psMetadataItem *fpaItem = psMetadataLookup(fpa->phu, keyword);
+            if (fpaItem) {
+                // XXX: Need to clean up before returning
+                return fpaItem;
+            }
+        }
+    }
+
+    // No header value
+    return NULL;
+}
+
+
+// Look for a default
+psMetadataItem *p_pmFPAConceptGetFromDefault(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
+                                            )
+{
+    bool mdOK = true;                   // Status of MD lookup
+    psMetadata *defaults = psMetadataLookupMD(&mdOK, fpa->camera, "DEFAULTS");
+    if (! mdOK) {
+        psError(PS_ERR_IO, false, "Unable to find DEFAULTS in camera configuration.\n");
+        return NULL;
+    }
+
+    psMetadataItem *defItem = psMetadataLookup(defaults, concept);
+    if (defItem) {
+        if (defItem->type == PS_DATA_METADATA) {
+            // A dependent default
+            psTrace(__func__, 7, "Evaluating dependent default....\n");
+            psMetadata *dependents = defItem->data.V; // The list of dependents
+            // Find out what it depends on
+            psString dependName = psStringCopy(concept);
+            psStringAppend(&dependName, ".DEPEND");
+            psString dependsOn = psMetadataLookupStr(&mdOK, defaults, dependName);
+            if (! mdOK) {
+                psError(PS_ERR_IO, false, "Unable to find %s in camera configuration for dependent default"
+                        " --- ignored\n", dependName);
+                // XXX: Need to clean up before returning
+                return NULL;
+            }
+            psFree(dependName);
+            // Find the value of the dependent concept
+            psMetadataItem *depItem = p_pmFPAConceptGetFromHeader(fpa, chip, cell, dependsOn);
+            if (! depItem) {
+                psError(PS_ERR_IO, true, "Unable to find value for %s (required for %s)\n", dependsOn,
+                        concept);
+                return NULL;
+            }
+            if (depItem->type != PS_DATA_STRING) {
+                psError(PS_ERR_IO, true, "Value of %s is not of type string, as required for dependency"
+                        " --- ignored.\n", dependsOn);
+            }
+
+            defItem = psMetadataLookup(dependents, depItem->data.V);    // This is now what we were after
+        }
+    }
+
+    // XXX: Need to clean up before returning
+    return defItem;                     // defItem is either NULL or points to what was desired
+}
+
+
+// Look for a database lookup
+// XXX: Not tested
+psMetadataItem *p_pmFPAConceptGetFromDB(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
+                                       )
+{
+    if (! db) {
+        // No database initialised
+        return NULL;
+    }
+
+    bool mdStatus = true;               // Status of MD lookup
+    psMetadata *database = psMetadataLookupMD(&mdStatus, fpa->camera, "DATABASE");
+    if (! mdStatus) {
+        // No error, because not everyone needs to use the DB
+        return NULL;
+    }
+
+    psMetadata *dbLookup = psMetadataLookupMD(&mdStatus, database, concept);
+    if (dbLookup) {
+        const char *tableName = psMetadataLookupStr(&mdStatus, dbLookup, "TABLE"); // Name of the table
+        //        const char *colName = psMetadataLookupStr(&mdStatus, dbLookup, "COLUMN"); // Name of the column
+        const char *givenCols = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENDBCOL"); // Name of "where"
+        // columns
+        const char *givenPS = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENPS"); // Values for "where"
+        // columns
+
+        // Now, need to get the "given"s
+        if (strlen(givenCols) || strlen(givenPS)) {
+            psList *cols = psStringSplit(givenCols, ",;"); // List of column names
+            psList *values = psStringSplit(givenPS, ",;"); // 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", concept);
+            } 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 *name = 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", concept);
+                    } else {
+                        // Search for the value name
+                        psMetadataItem *item = p_pmFPAConceptGetFromHeader(fpa, chip, cell, name);
+                        if (! item) {
+                            item = p_pmFPAConceptGetFromDefault(fpa, chip, cell, name);
+                        }
+                        if (! item) {
+                            psLogMsg(__func__, PS_LOG_ERROR, "Unable to find the value name %s for DB "
+                                     " lookup on %s --- ignored.\n", name, concept);
+                        } 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(concept, item->type,
+                                                      item->comment, item->data.V);
+                            psMetadataAddItem(selection, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+                            psFree(newItem);
+                        }
+                    }
+                    psFree(name);
+                    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 *result = 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",
+                             concept);
+                } else {
+                    if (dbResult-> n > 1) {
+                        psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s --- "
+                                 " using the first one only.\n", concept);
+                    }
+                    result = (psMetadataItem*)dbResult->data[0];
+                }
+                // XXX: Need to clean up before returning
+                return result;
+            }
+            psFree(cols);
+            psFree(values);
+        }
+    } // Doing the "given"s.
+
+    psAbort(__func__, "Shouldn't ever get here.\n");
+    return NULL;
+}
+
+
+// Concept lookup
+psMetadataItem *p_pmFPAConceptGet(pmFPA *fpa, // The FPA
+                                  pmChip *chip,// The chip
+                                  pmCell *cell, // The cell
+                                  psDB *db, // DB handle
+                                  const char *concept // Concept name
+                                 )
+{
+    // Try headers, database, defaults in order
+    psMetadataItem *item = p_pmFPAConceptGetFromCamera(cell, concept);
+    if (! item) {
+        item = p_pmFPAConceptGetFromHeader(fpa, chip, cell, concept);
+    }
+    if (! item) {
+        item = p_pmFPAConceptGetFromDB(fpa, chip, cell, db, concept);
+    }
+    if (! item) {
+        item = p_pmFPAConceptGetFromDefault(fpa, chip, cell, concept);
+    }
+    return item; // item is either NULL, or points to what was desired
+}
+
+
+void p_pmFPAConceptGetF32(pmFPA *fpa,   // The FPA
+                          pmChip *chip, // The chip
+                          pmCell *cell, // The cell
+                          psDB *db,     // DB handle
+                          psMetadata *concepts, // The concepts MD
+                          const char *name, // Name of the concept
+                          const char *comment // Comment for concept
+                         )
+{
+    psMetadataItem *item = p_pmFPAConceptGet(fpa, chip, cell, db, name);
+    float value = NAN;
+    if (item) {
+        switch (item->type) {
+        case PS_DATA_F32:
+            value = item->data.F32;
+            break;
+        case PS_DATA_F64:
+            // Assume it's OK to truncate to floating point from double
+            value = (float)item->data.F64;
+            break;
+        case PS_DATA_S32:
+            // Promote to float
+            value = (float)item->data.S32;
+            break;
+        default:
+            psError(PS_ERR_IO, true, "Concept %s (%s) is not of floating point type (%x) --- treating as "
+                    "undefined.\n", name, comment, item->type);
+        }
+    } else {
+        psError(PS_ERR_IO, true, "Concept %s (%s) is not defined.\n", name, comment);
+    }
+    psTrace(__func__, 7, "Adding %s (%s): %f\n", name, comment, value);
+
+    psMetadataAdd(concepts, PS_LIST_TAIL, name, PS_TYPE_F32 | PS_META_REPLACE, comment, value);
+}
+
+void p_pmFPAConceptGetF64(pmFPA *fpa,   // The FPA
+                          pmChip *chip, // The chip
+                          pmCell *cell, // The cell
+                          psDB *db,     // DB handle
+                          psMetadata *concepts, // The concepts MD
+                          const char *name, // Name of the concept
+                          const char *comment // Comment for concept
+                         )
+{
+    psMetadataItem *item = p_pmFPAConceptGet(fpa, chip, cell, db, name);
+    double value = NAN;
+    if (item) {
+        switch (item->type) {
+        case PS_TYPE_F64:
+            value = item->data.F64;
+            break;
+        case PS_TYPE_F32:
+            // Promote to double
+            value = (double)item->data.F32;
+            break;
+        case PS_TYPE_S32:
+            // Promote to double
+            value = (double)item->data.S32;
+            break;
+        default:
+            psError(PS_ERR_IO, true, "Concept %s (%s) is not of double-precision floating point type (%x) "
+                    "--- treating as undefined.\n", name, comment, item->type);
+        }
+    } else {
+        psError(PS_ERR_IO, true, "Concept %s (%s) is not defined.\n", name, comment);
+    }
+    psTrace(__func__, 7, "Adding %s (%s): %f\n", name, comment, value);
+
+    psMetadataAdd(concepts, PS_LIST_TAIL, name, PS_TYPE_F64 | PS_META_REPLACE, comment, value);
+}
+
+void p_pmFPAConceptGetS32(pmFPA *fpa,   // The FPA
+                          pmChip *chip, // The chip
+                          pmCell *cell, // The cell
+                          psDB *db,     // DB handle
+                          psMetadata *concepts, // The concepts MD
+                          const char *name, // Name of the concept
+                          const char *comment // Comment for concept
+                         )
+{
+    psMetadataItem *item = p_pmFPAConceptGet(fpa, chip, cell, db, name);
+    int value = 0;
+    if (item) {
+        switch (item->type) {
+        case PS_TYPE_S32:
+            value = item->data.S32;
+            break;
+        case PS_TYPE_F32:
+            psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F32 --- converting.\n",
+                     name, comment);
+            value = (int)item->data.F32;
+            break;
+        case PS_TYPE_F64:
+            psLogMsg(__func__, PS_LOG_WARN, "Concept %s (%s) should be S32, but is F64 --- converting.\n",
+                     name, comment);
+            value = (int)item->data.F64;
+            break;
+        default:
+            psError(PS_ERR_IO, true, "Concept %s (%s) is not of integer type (%x) --- treating as "
+                    "undefined.\n", name, comment, item->type);
+        }
+    } else {
+        psError(PS_ERR_IO, true, "Concept %s (%s) is not defined.\n", name, comment);
+    }
+    psTrace(__func__, 7, "Adding %s (%s): %d\n", name, comment, value);
+
+    psMetadataAdd(concepts, PS_LIST_TAIL, name, PS_TYPE_S32 | PS_META_REPLACE, comment, value);
+}
+
+void p_pmFPAConceptGetString(pmFPA *fpa, // The FPA
+                             pmChip *chip, // The chip
+                             pmCell *cell, // The cell
+                             psDB *db,  // DB handle
+                             psMetadata *concepts, // The concepts MD
+                             const char *name, // Name of the concept
+                             const char *comment // Comment for concept
+                            )
+{
+    psMetadataItem *item = p_pmFPAConceptGet(fpa, chip, cell, db, name);
+    psString value = NULL;
+    if (item) {
+        switch (item->type) {
+        case PS_DATA_STRING:
+            value = psMemIncrRefCounter(item->data.V);
+            break;
+        case PS_DATA_F32:
+            psStringAppend(&value, "%f", item->data.F32);
+            break;
+        case PS_DATA_S32:
+            psStringAppend(&value, "%d", item->data.S32);
+            break;
+        default:
+            psError(PS_ERR_IO, true, "Concept %s (%s) is not of string type (%x) --- treating as "
+                    "undefined.\n", name, comment, item->type);
+        }
+    } else {
+        psError(PS_ERR_IO, true, "Concept %s (%s) is not defined.\n", name, comment);
+        value = psStringCopy("");
+    }
+    psTrace(__func__, 7, "Adding %s (%s): %s\n", name, comment, value);
+
+    psMetadataAdd(concepts, PS_LIST_TAIL, name, PS_DATA_STRING | PS_META_REPLACE, comment, value);
+    psFree(value);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Ingest concepts for the FPA
+void pmFPAIngestConcepts(pmFPA *fpa,    // The FPA
+                         psDB *db       // DB handle
+                        )
+{
+    if (! fpa->concepts) {
+        fpa->concepts = psMetadataAlloc();
+    }
+
+    // FPA.NAME
+    p_pmFPAConceptGetString(fpa, NULL, NULL, db, fpa->concepts, "FPA.NAME", "Name of FPA");
+
+    // FPA.AIRMASS
+    p_pmFPAConceptGetF32(fpa, NULL, NULL, db, fpa->concepts, "FPA.AIRMASS", "Airmass at boresight");
+
+    // FPA.FILTER
+    p_pmFPAConceptGetString(fpa, NULL, NULL, db, fpa->concepts, "FPA.FILTER", "Filter used");
+
+    // FPA.POSANGLE
+    p_pmFPAConceptGetF32(fpa, NULL, NULL, db, fpa->concepts, "FPA.POSANGLE", "Position angle for instrument");
+
+    // FPA.RADECSYS
+    p_pmFPAConceptGetString(fpa, NULL, NULL, db, fpa->concepts, "FPA.RADECSYS", "Celestial coordinate system");
+
+    // These take some extra work
+
+    // FPA.RA
+    {
+        double ra = NAN;                // The RA
+        psMetadataItem *raItem = p_pmFPAConceptGet(fpa, NULL, NULL, db, "FPA.RA"); // The FPA.RA item
+        if (raItem)
+        {
+            switch (raItem->type) {
+            case PS_TYPE_F32:
+                ra = raItem->data.F32;
+                break;
+            case PS_TYPE_F64:
+                ra = raItem->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(raItem->data.V, "%d:%d:%f", &big, &medium, &small) != 3 &&
+                            sscanf(raItem->data.V, "%d %d %f", &big, &medium, &small) != 3)
+                    {
+                        psError(PS_ERR_IO, true, "Cannot interpret FPA.RA: %s\n", raItem->data.V);
+                        break;
+                    }
+                    ra = abs(big) + (float)medium/60.0 + small/3600.0;
+                    if (big < 0)
+                    {
+                        ra *= -1.0;
+                    }
+                }
+                break;
+            default:
+                psError(PS_ERR_IO, true, "FPA.RA is of an unexpected type: %x\n", raItem->type);
+            }
+
+            // How to interpret the RA
+            const psMetadata *camera = fpa->camera; // Camera configuration data
+            bool mdok = true;           // Status of MD lookup
+            psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+            if (mdok && formats) {
+                psString raFormat = psMetadataLookupStr(&mdok, formats, "FPA.RA");
+                if (mdok && strlen(raFormat) > 0) {
+                    if (strcasecmp(raFormat, "HOURS") == 0) {
+                        ra *= M_PI / 12.0;
+                    } else if (strcasecmp(raFormat, "DEGREES") == 0) {
+                        ra *= M_PI / 180.0;
+                    } else if (strcasecmp(raFormat, "RADIANS") == 0) {
+                        // No action required
+                    } else {
+                        psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.RA in FORMATS (%s) --- assuming"
+                                 " HOURS.\n");
+                        ra *= M_PI / 12.0;
+                    }
+                } else {
+                    psError(PS_ERR_IO, false, "Unable to find FPA.RA in FORMATS --- assuming HOURS.\n");
+                    ra *= M_PI / 12.0;
+                }
+            } else {
+                psError(PS_ERR_IO, false, "Unable to find FORMAT metadata in camera configuration --- "
+                        "assuming format for FPA.RA is HOURS.\n");
+                ra *= M_PI / 12.0;
+            }
+        } else
+        {
+            psError(PS_ERR_IO, false, "Couldn't find FPA.RA.\n");
+        }
+
+        psMetadataAdd(fpa->concepts, PS_LIST_TAIL, "FPA.RA", PS_DATA_F64 | PS_META_REPLACE,
+                      "Right Ascension of the boresight (radians)", ra);
+
+    }
+
+    // FPA.DEC
+    {
+        double dec = NAN;               // The DEC
+        psMetadataItem *decItem = p_pmFPAConceptGet(fpa, NULL, NULL, db, "FPA.DEC"); // The FPA.DEC item
+        if (decItem) {
+            switch (decItem->type) {
+            case PS_TYPE_F32:
+                dec = decItem->data.F32;
+                break;
+            case PS_TYPE_F64:
+                dec = decItem->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(decItem->data.V, "%d:%d:%f", &big, &medium, &small) != 3 &&
+                            sscanf(decItem->data.V, "%d %d %f", &big, &medium, &small) != 3)
+                    {
+                        psError(PS_ERR_IO, true, "Cannot interpret FPA.DEC: %s\n", decItem->data.V);
+                        break;
+                    }
+                    dec = abs(big) + (float)medium/60.0 + small/3600.0;
+                    if (big < 0)
+                    {
+                        dec *= -1.0;
+                    }
+                }
+                break;
+            default:
+                psError(PS_ERR_IO, true, "FPA.DEC is of an unexpected type: %x\n", decItem->type);
+            }
+
+            // How to interpret the DEC
+            const psMetadata *camera = fpa->camera; // Camera configuration data
+            bool mdok = true;           // Status of MD lookup
+            psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+            if (mdok && formats) {
+                psString decFormat = psMetadataLookupStr(&mdok, formats, "FPA.DEC");
+                if (mdok && strlen(decFormat) > 0) {
+                    if (strcasecmp(decFormat, "HOURS") == 0) {
+                        dec *= M_PI / 12.0;
+                    } else if (strcasecmp(decFormat, "DEGREES") == 0) {
+                        dec *= M_PI / 180.0;
+                    } else if (strcasecmp(decFormat, "RADIANS") == 0) {
+                        // No action required
+                    } else {
+                        psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.DEC in FORMATS (%s) --- "
+                                 "assuming DEGREES.\n");
+                        dec *= M_PI / 180.0;
+                    }
+                } else {
+                    psError(PS_ERR_IO, false, "Unable to find FPA.DEC in FORMATS --- assuming DEGREES.\n");
+                    dec *= M_PI / 180.0;
+                }
+            } else {
+                psError(PS_ERR_IO, false, "Unable to find FORMATS metadata in camera configuration --- "
+                        "assuming format for FPA.DEC is DEGREES.\n");
+                dec *= M_PI / 180.0;
+            }
+        } else {
+            psError(PS_ERR_IO, false, "Couldn't find FPA.DEC.\n");
+        }
+
+        psMetadataAdd(fpa->concepts, PS_LIST_TAIL, "FPA.DEC", PS_DATA_F64 | PS_META_REPLACE,
+                      "Declination of the boresight (radians)", dec);
+
+    }
+
+    // Pau.
+}
+
+
+// Ingest concepts for the chip
+bool pmChipIngestConcepts(pmChip *chip, // The chip
+                          psDB *db      // DB handle
+                         )
+{
+    //    pmFPA *fpa = chip->parent;          // The parent FPA
+
+    if (! chip->concepts) {
+        chip->concepts = psMetadataAlloc();
+    }
+
+    // CHIP.NAME --- added by pmFPAConstruct
+
+    // Pau.
+    return true;
+}
+
+
+// Add corrective to a position --- in case the user wants FORTRAN indexing (like the FITS standard...)
+static bool correctPosition(pmFPA *fpa, // FPA, contains the camera configuration
+                            pmCell *cell, // Cell containing the concept to correct
+                            const char *conceptName // Name of concept to correct
+                           )
+{
+    bool mdok = false;              // Result of MD lookup
+    psMetadata *formats = psMetadataLookupMD(&mdok, fpa->camera, "FORMATS");
+    if (mdok && formats) {
+        psString format = psMetadataLookupStr(&mdok, formats, conceptName);
+        if (mdok && strlen(format) > 0 && strcasecmp(format, "FORTRAN") == 0) {
+            psMetadataItem *valueItem = psMetadataLookup(cell->concepts, conceptName);
+            valueItem->data.S32 -= 1;
+        }
+    }
+    return true;
+}
+
+bool p_pmCellIngestConcept(pmFPA *fpa,  // The FPA
+                           pmChip *chip, // The chip
+                           pmCell *cell, // The cell
+                           psDB *db,    // DB handle
+                           const char *concept // Name of the concept
+                          )
+{
+    if (strcmp(concept, "CELL.GAIN") == 0) {
+        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.GAIN", "CCD gain (e/count)");
+    } else if (strcmp(concept, "CELL.READNOISE") == 0) {
+        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.READNOISE", "CCD read noise (e)");
+    } else if (strcmp(concept, "CELL.SATURATION") == 0) {
+        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.SATURATION",
+                             "Saturation level (ADU)");
+    } else if (strcmp(concept, "CELL.BAD") == 0) {
+        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.BAD", "Bad level (ADU)");
+    } else if (strcmp(concept, "CELL.XPARITY") == 0) {
+        p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.XPARITY",
+                             "Orientation in x compared to the rest of the FPA");
+    } else if (strcmp(concept, "CELL.YPARITY") == 0) {
+        p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.YPARITY",
+                             "Orientation in y compared to the rest of the FPA");
+    } else if (strcmp(concept, "CELL.READDIR") == 0) {
+        p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.READDIR",
+                             "Read direction: 1=row, 2=col");
+    } else if (strcmp(concept, "CELL.EXPOSURE") == 0) { // used to be READOUT.EXPOSURE
+        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.EXPOSURE", "Exposure time (sec)");
+    } else if (strcmp(concept, "CELL.DARKTIME") == 0) { // used to be READOUT.DARKTIME
+        p_pmFPAConceptGetF32(fpa, chip, cell, db, cell->concepts, "CELL.DARKTIME",
+                             "Time since CCD flush (sec)");
+        // These take some extra work
+    } else if (strcmp(concept, "CELL.TRIMSEC") == 0) {
+        psRegion *trimsec = psAlloc(sizeof(psRegion)); // Make space for a psRegion (usually passed by value)
+
+        psMetadataItem *secItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TRIMSEC");
+        if (! secItem) {
+            psError(PS_ERR_IO, false, "Couldn't find CELL.TRIMSEC.\n");
+            *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+        } else if (secItem->type != PS_DATA_STRING) {
+            psError(PS_ERR_IO, true, "CELL.TRIMSEC is not of type STR (%x)\n", secItem->type);
+            *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+        } else {
+            psString section = secItem->data.V; // The section string
+
+            psMetadataItem *sourceItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TRIMSEC.SOURCE");
+            if (! sourceItem) {
+                psError(PS_ERR_IO, false, "Couldn't find CELL.TRIMSEC.SOURCE.\n");
+                *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+            } else if (sourceItem->type != PS_DATA_STRING) {
+                psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
+                *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+            } else {
+                psString source = sourceItem->data.V; // The source string
+
+                if (strcasecmp(source, "VALUE") == 0) {
+                    *trimsec = psRegionFromString(section);
+                } else if (strcasecmp(source, "HEADER") == 0) {
+                    psMetadata *header = NULL; // The FITS header
+                    if (cell->hdu) {
+                        header = cell->hdu->header;
+                    } else if (chip->hdu) {
+                        header = chip->hdu->header;
+                    } else if (fpa->hdu) {
+                        header = fpa->hdu->header;
+                    }
+                    if (! header) {
+                        psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+                        *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+                    } else {
+                        bool mdok = true;               // Status of MD lookup
+                        psString secValue = psMetadataLookupStr(&mdok, header, section);
+                        if (! mdok || ! secValue) {
+                            psError(PS_ERR_IO, false, "Unable to locate header %s\n", section);
+                            *trimsec = psRegionSet(0.0, 0.0, 0.0, 0.0);
+                        } else {
+                            *trimsec = psRegionFromString(secValue);
+                        }
+                    }
+                } else {
+                    psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE (%s) is not HEADER or VALUE --- trying "
+                            "VALUE.\n", source);
+                    *trimsec = psRegionFromString(section);
+                } // Value of CELL.TRIMSEC.SOURCE
+            } // Looking up CELL.TRIMSEC.SOURCE
+        } // Looking up CELL.TRIMSEC
+
+        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.TRIMSEC", PS_DATA_UNKNOWN | PS_META_REPLACE,
+                      "Trim section", trimsec);
+        psFree(trimsec);
+
+    } else if (strcmp(concept, "CELL.BIASSEC") == 0) {
+        psList *biassecs = psListAlloc(NULL); // List of bias sections
+
+        psMetadataItem *secItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.BIASSEC");
+        if (! secItem) {
+            psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.\n");
+        } else if (secItem->type != PS_DATA_STRING) {
+            psError(PS_ERR_IO, true, "CELL.BIASSEC is not of type STR (%x)\n", secItem->type);
+        } else {
+            psString sections = secItem->data.V; // The section string
+
+            psMetadataItem *sourceItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.BIASSEC.SOURCE");
+            if (! sourceItem) {
+                psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.SOURCE.\n");
+            } else if (sourceItem->type != PS_DATA_STRING) {
+                psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
+            } else {
+                psString source = sourceItem->data.V; // The source string
+
+                psList *secList = psStringSplit(sections, " ;"); // List of sections
+                psListIterator *secIter = psListIteratorAlloc(secList, PS_LIST_HEAD, false); // Iterator over
+                // sections
+                psString aSection = NULL; // A section from the list
+                while ((aSection = psListGetAndIncrement(secIter))) {
+                    psRegion *region = psAlloc(sizeof(psRegion)); // Make space for a psRegion (usually passed
+                    // by value)
+
+                    if (strcasecmp(source, "VALUE") == 0) {
+                        *region = psRegionFromString(aSection);
+                    } else if (strcasecmp(source, "HEADER") == 0) {
+                        psMetadata *header = NULL; // The FITS header
+                        if (cell->hdu) {
+                            header = cell->hdu->header;
+                        } else if (chip->hdu) {
+                            header = chip->hdu->header;
+                        } else if (fpa->hdu) {
+                            header = fpa->hdu->header;
+                        }
+                        if (! header) {
+                            psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+                            *region = psRegionSet(0.0,0.0,0.0,0.0);
+                        } else {
+                            bool mdok = true;           // Status of MD lookup
+                            psString secValue = psMetadataLookupStr(&mdok, header, aSection);
+                            if (! mdok || ! secValue) {
+                                psError(PS_ERR_IO, false, "Unable to locate header %s\n", aSection);
+                                *region = psRegionSet(0.0,0.0,0.0,0.0);
+                            } else {
+                                *region = psRegionFromString(secValue);
+                            }
+                        }
+                    } else {
+                        psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE (%s) is not HEADER or VALUE --- trying "
+                                "VALUE.\n", source);
+                        *region = psRegionFromString(aSection);
+                    } // Value of CELL.BIASSEC.SOURCE
+
+                    psListAdd(biassecs, PS_LIST_TAIL, region);
+                    psFree(region);
+                } // Iterating over multiple sections
+                psFree(secIter);
+                psFree(secList);
+            } // Looking up CELL.BIASSEC.SOURCE
+        } // Looking up CELL.BIASSEC
+
+        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.BIASSEC", PS_DATA_LIST | PS_META_REPLACE,
+                      "Bias sections", biassecs);
+        psFree(biassecs);
+
+    } else if (strcmp(concept, "CELL.XBIN") == 0) {
+        int xBin = 1;                   // Binning factor in x
+        psMetadataItem *binItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.XBIN");
+        if (! binItem) {
+            psError(PS_ERR_IO, false, "Couldn't find CELL.XBIN.\n");
+        } else if (binItem->type == PS_DATA_STRING) {
+            psString binString = binItem->data.V; // The string containing the binning
+            if (sscanf(binString, "%d %*d", &xBin) != 1 &&
+                    sscanf(binString, "%d,%*d", &xBin) != 1) {
+                psError(PS_ERR_IO, true, "Unable to read string to get x binning: %s\n", binString);
+            }
+        } else if (binItem->type == PS_TYPE_S32) {
+            xBin = binItem->data.S32;
+        } else {
+            psError(PS_ERR_IO, true, "Note sure how to interpret CELL.XBIN of type %x --- assuming 1.\n",
+                    binItem->type);
+        }
+
+        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.XBIN", PS_TYPE_S32 | PS_META_REPLACE,
+                      "Binning in x", xBin);
+
+    } else if (strcmp(concept, "CELL.YBIN") == 0) {
+        int yBin = 1;                   // Binning factor in y
+        psMetadataItem *binItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.YBIN");
+        if (! binItem) {
+            psError(PS_ERR_IO, false, "Couldn't find CELL.YBIN.\n");
+        } else if (binItem->type == PS_DATA_STRING) {
+            psString binString = binItem->data.V; // The string containing the binning
+            if (sscanf(binString, "%*d %d", &yBin) != 1 &&
+                    sscanf(binString, "%*d,%d", &yBin) != 1) {
+                psError(PS_ERR_IO, true, "Unable to read string to get y binning: %s\n", binString);
+            }
+        } else if (binItem->type == PS_TYPE_S32) {
+            yBin = binItem->data.S32;
+        } else {
+            psError(PS_ERR_IO, true, "Note sure how to interpret CELL.YBIN of type %x --- assuming 1.\n",
+                    binItem->type);
+        }
+
+        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.YBIN", PS_TYPE_S32 | PS_META_REPLACE,
+                      "Binning in y", yBin);
+
+    } else if (strcmp(concept, "CELL.TIME") == 0 || strcmp(concept, "CELL.TIMESYS") == 0) {
+        // Do CELL.TIME and CELL.TIMESYS together
+        psTime *time = NULL;            // The time
+        psTimeType timeSys = PS_TIME_UTC; // The time system
+
+        // CELL.TIMESYS
+        psMetadataItem *sysItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TIMESYS");
+        if (! sysItem) {
+            psError(PS_ERR_IO, true, "Couldn't find CELL.TIMESYS --- assuming UTC.\n");
+        } else if (sysItem->type != PS_DATA_STRING) {
+            psError(PS_ERR_IO, true, "CELL.TIMESYS isn't of type STRING --- assuming UTC.\n");
+        } else {
+            psString sys = sysItem->data.V; // The time system string
+            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_IO, true, "Can't interpret CELL.TIMESYS --- assuming UTC.\n");
+            }
+        }
+        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.TIMESYS", PS_TYPE_S32 | PS_META_REPLACE,
+                      "Time system", timeSys);
+
+        psMetadataItem *timeItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TIME");
+        if (! timeItem) {
+            psError(PS_ERR_IO, false, "Couldn't find CELL.TIME.\n");
+        } else {
+            // Get format
+            const psMetadata *camera = fpa->camera; // The camera configuration data
+            bool mdok = true;           // Status of MD lookup
+            psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+            if (mdok && formats) {
+                psString timeFormat = psMetadataLookupStr(&mdok, formats, "CELL.TIME");
+                if (mdok && strlen(timeFormat) > 0) {
+                    switch (timeItem->type) {
+                    case PS_DATA_STRING: {
+                            psString timeString = timeItem->data.V;     // String with the time
+                            if (strcasecmp(timeFormat, "ISO") == 0) {
+                                // timeString contains an ISO time
+                                time = psTimeFromISO(timeString, timeSys);
+                            } else if (strcasecmp(timeFormat, "JD") == 0) {
+                                double timeValue = strtod (timeString, NULL);
+                                time = psTimeFromJD(timeValue);
+                            } else if (strcasecmp(timeFormat, "MJD") == 0) {
+                                double timeValue = strtod (timeString, NULL);
+                                time = psTimeFromMJD(timeValue);
+                            } else if (strstr(timeFormat, "SEPARATE")) {
+                                // timeString contains headers for the date and time
+                                psMetadata *header = NULL; // The FITS header
+                                if (cell->hdu) {
+                                    header = cell->hdu->header;
+                                } else if (chip->hdu) {
+                                    header = chip->hdu->header;
+                                } else if (fpa->hdu) {
+                                    header = fpa->hdu->header;
+                                }
+                                if (! header) {
+                                    psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+                                } else {
+                                    // Get the headers
+                                    char *stuff1 = strpbrk(timeString, " ,;");
+                                    psString dateName = psStringNCopy(timeString,
+                                                                      strlen(timeString) - strlen(stuff1));
+                                    char *stuff2 = strpbrk(stuff1, "abcdefghijklmnopqrstuvwxyz"
+                                                           "ABCDEFGHIJKLMNOPQRSTUVWXYZ");
+                                    psString timeName = psStringCopy(stuff2);
+
+                                    bool mdok = true; // Status of MD lookup
+                                    psString dateString = psMetadataLookupStr(&mdok, header, dateName);
+                                    psFree(dateName);
+                                    int day = 0, month = 0, year = 0;
+                                    if (sscanf(dateString, "%d-%d-%d", &day, &month, &year) != 3 &&
+                                            sscanf(dateString, "%d/%d/%d", &day, &month, &year) != 3) {
+                                        psError(PS_ERR_IO, true, "Unable to read date: %s\n", dateString);
+                                    } else {
+                                        if (strstr(timeFormat, "BACKWARDS")) {
+                                            int temp = day;
+                                            day = year;
+                                            year = temp;
+                                        }
+                                        if (strstr(timeFormat, "PRE2000") || year < 2000) {
+                                            year += 2000;
+                                        }
+
+                                        psMetadataItem *timeItem = psMetadataLookup(header, timeName);
+                                        if (! timeItem) {
+                                            psError(PS_ERR_IO, false, "Unable to find time header: %s\n",
+                                                    timeName);
+                                        } else if (timeItem->type == PS_DATA_STRING) {
+                                            // Time is a string, in the usual way:
+                                            psStringAppend(&dateString, "T%s", timeItem->data.V);
+                                        } else {
+                                            // Assume that time is specified in Second of Day
+                                            double seconds = NAN;
+                                            switch (timeItem->type) {
+                                            case PS_TYPE_S32:
+                                                seconds = timeItem->data.S32;
+                                                break;
+                                            case PS_TYPE_F32:
+                                                seconds = timeItem->data.F32;
+                                                break;
+                                            case PS_TYPE_F64:
+                                                seconds = timeItem->data.F64;
+                                                break;
+                                            default:
+                                                psError(PS_ERR_IO, true, "Time header (%s) is not of an "
+                                                        "expected type: %x\n", timeName, timeItem->type);
+                                            }
+                                            // 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(&dateString, "T%02d:%02d:%02f", hours, minutes,
+                                                           seconds);
+                                        }
+                                        time = psTimeFromISO(dateString, timeSys);
+                                    } // Reading date and time
+                                    psFree(timeName);
+                                } // Reading headers
+                            } else {
+                                psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%s) --- trying "
+                                        "ISO\n", timeString);
+                                time = psTimeFromISO(timeString, timeSys);
+                            } // Interpreting the time string
+                        }
+                        break;
+                    case PS_TYPE_F32: {
+                            double timeValue = (double)timeItem->data.F32;
+                            if (strcasecmp(timeFormat, "JD") == 0) {
+                                time = psTimeFromJD(timeValue);
+                            } else if (strcasecmp(timeFormat, "MJD") == 0) {
+                                time = psTimeFromMJD(timeValue);
+                            } else {
+                                psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%f) --- trying "
+                                        "JD\n", timeValue);
+                                time = psTimeFromJD(timeValue);
+                            }
+                        }
+                        break;
+                    case PS_TYPE_F64: {
+                            double timeValue = (double)timeItem->data.F64;
+                            if (strcasecmp(timeFormat, "JD") == 0) {
+                                time = psTimeFromJD(timeValue);
+                            } else if (strcasecmp(timeFormat, "MJD") == 0) {
+                                time = psTimeFromMJD(timeValue);
+                            } else {
+                                psError(PS_ERR_IO, true, "Not sure how to parse CELL.TIME (%f) --- trying "
+                                        "JD\n", timeValue);
+                                time = psTimeFromJD(timeValue);
+                            }
+                        }
+                        break;
+                    default:
+                        psError(PS_ERR_IO, true, "Unable to parse CELL.TIME.\n");
+                    }
+                } else {
+                    psError(PS_ERR_IO, false, "Unable to find CELL.TIME in FORMATS.\n");
+                } // Getting the format
+            } else {
+                psError(PS_ERR_IO, false, "Unable to find FORMATS in camera configuration.\n");
+            } // Getting the formats
+        } // Getting CELL.TIME
+
+        psMetadataAdd(cell->concepts, PS_LIST_TAIL, "CELL.TIME", PS_DATA_TIME | PS_META_REPLACE,
+                      "Time of exposure", time);
+        psFree(time);
+
+        // These are new and experimental concepts: CELL.X0 and CELL.Y0
+    } else if (strcmp(concept, "CELL.X0") == 0) {
+        p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.X0", "Position of (0,0) on the chip");
+        correctPosition(fpa, cell, "CELL.X0");
+    } else if (strcmp(concept, "CELL.Y0") == 0) {
+        p_pmFPAConceptGetS32(fpa, chip, cell, db, cell->concepts, "CELL.Y0", "Position of (0,0) on the chip");
+        correctPosition(fpa, cell, "CELL.Y0");
+    }
+
+    return true;
+}
+
+
+
+// Ingest concepts for the cell
+bool pmCellIngestConcepts(pmCell *cell, // The cell
+                          psDB *db      // DB handle
+                         )
+{
+    pmChip *chip = cell->parent;        // The parent chip
+    pmFPA *fpa = chip->parent;          // The parent FPA
+
+    if (! cell->concepts) {
+        cell->concepts = psMetadataAlloc();
+    }
+
+    // CELL.NAME --- added by pmFPAConstruct
+
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.GAIN");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.READNOISE");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.SATURATION");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.BAD");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.XPARITY");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.YPARITY");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.READDIR");
+
+    // These used to be pmReadoutGetExposure and pmReadoutGetDarkTime, 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....
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.EXPOSURE");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.DARKTIME");
+
+
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.TRIMSEC");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.BIASSEC");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.TIME"); // This also does CELL.TIMESYS
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.XBIN");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.YBIN");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.X0");
+    p_pmCellIngestConcept(fpa, chip, cell, db, "CELL.Y0");
+
+    return true;
+}
+
+
+// Retrieve a concept
+psMetadataItem *pmCellGetConcept(pmCell *cell, // The cell
+                                 const char *concept // The concept
+                                )
+{
+    psMetadataItem *item = psMetadataLookup(cell->concepts, concept);
+    if (! item) {
+        pmChip *chip = cell->parent;
+        item = psMetadataLookup(chip->concepts, concept);
+        if (! item) {
+            pmFPA *fpa = chip->parent;
+            item = psMetadataLookup(fpa->concepts, concept);
+        }
+    }
+    return item;                        // item is either NULL or is what we want.
+}
+
+
+// Set up concepts for blank image
+bool pmFPABlankConcepts(pmFPA *fpa      // The FPA
+                       )
+{
+    assert(fpa);
+
+    // FPA-level concepts
+    if (! fpa->concepts) {
+        fpa->concepts = psMetadataAlloc();
+    }
+    psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.NAME", PS_META_REPLACE, "Name of FPA", "");
+    psMetadataAddF32(fpa->concepts, PS_LIST_TAIL, "FPA.AIRMASS", PS_META_REPLACE,
+                     "Airmass at boresight", NAN);
+    psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.FILTER", PS_META_REPLACE, "Filter used", "");
+    psMetadataAddF32(fpa->concepts, PS_LIST_TAIL, "FPA.POSANGLE", PS_META_REPLACE,
+                     "Position angle for instrument", NAN);
+    psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "FPA.RADECSYS", PS_META_REPLACE,
+                     "Celestial coordinate system", "");
+    psMetadataAddF64(fpa->concepts, PS_LIST_TAIL, "FPA.RA", PS_META_REPLACE,
+                     "Right Ascension of the boresight (radians)", NAN);
+    psMetadataAddF64(fpa->concepts, PS_LIST_TAIL, "FPA.DEC", PS_META_REPLACE,
+                     "Declination of the boresight (radians)", NAN);
+
+    // Iterate through the chips
+    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) {
+            continue;
+        }
+
+        // Chip-level concepts
+        if (! chip->concepts) {
+            chip->concepts = psMetadataAlloc();
+        }
+        // CHIP.NAME *should* have been added by pmFPAConstruct --- make sure
+        psMetadataItem *chipNameItem = psMetadataLookup(chip->concepts, "CHIP.NAME");
+        if (! chipNameItem) {
+            psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "CHIP.NAME", PS_META_REPLACE, "Name of Chip", "");
+        }
+
+        // Iterate through the cells
+        psArray *cells = chip->cells;   // Array of cells
+        for (int j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[i]; // The cell of interest
+            if (! cell) {
+                continue;
+            }
+
+            // Cell-level concepts
+            if (! cell->concepts) {
+                cell->concepts = psMetadataAlloc();
+            }
+            // CELL.NAME *should* have been added by pmFPAConstruct --- make sure
+            psMetadataItem *cellNameItem = psMetadataLookup(cell->concepts, "CELL.NAME");
+            if (!cellNameItem) {
+                psMetadataAddStr(fpa->concepts, PS_LIST_TAIL, "CELL.NAME", PS_META_REPLACE,
+                                 "Name of Cell", "");
+            }
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.GAIN", PS_META_REPLACE, "CCD gain (e/count)", NAN);
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.READNOISE", PS_META_REPLACE, "CCD read noise (e)", NAN);
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.SATURATION", PS_META_REPLACE, "Saturation level (ADU)", NAN);
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.BAD", PS_META_REPLACE, "Bad level (ADU)", NAN);
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.XPARITY", PS_META_REPLACE, "Orientation in x compared to the rest of the FPA", NAN);
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.YPARITY", PS_META_REPLACE, "Orientation in x compared to the rest of the FPA", NAN);
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.READDIR", PS_META_REPLACE, "Read direction: 1=row, 2=col", NAN);
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.EXPOSURE", PS_META_REPLACE, "Exposure time (sec)", NAN);
+            psMetadataAddF32(cell->concepts, PS_LIST_TAIL, "CELL.DARKTIME", PS_META_REPLACE, "Time since CCD flush (sec)", NAN);
+            psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.XBIN", PS_META_REPLACE, "Binning in x", 0);
+            psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.YBIN", PS_META_REPLACE, "Binning in y", 0);
+            psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.X0", PS_META_REPLACE, "Position of (0,0) on the chip", 0);
+            psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.Y0", PS_META_REPLACE, "Position of (0,0) on the chip", 0);
+
+            psRegion *trimsec = psAlloc(sizeof(psRegion)); // Dummy region
+            trimsec->x0 = NAN;
+            trimsec->x1 = NAN;
+            trimsec->y0 = NAN;
+            trimsec->y1 = NAN;
+            psMetadataAddPtr(cell->concepts, PS_LIST_TAIL, "CELL.TRIMSEC", PS_META_REPLACE, "Trim section", trimsec);
+            psList *biassecs = psListAlloc(NULL); // Dummy list
+            psMetadataAddPtr(cell->concepts, PS_LIST_TAIL, "CELL.BIASSEC", PS_META_REPLACE, "Bias section", biassecs);
+            psTime *time = psTimeAlloc(PS_TIME_TAI); // Dummy time
+            psMetadataAddPtr(cell->concepts, PS_LIST_TAIL, "CELL.TIME", PS_META_REPLACE, "Time of exposure", time);
+            psMetadataAddS32(cell->concepts, PS_LIST_TAIL, "CELL.TIMESYS", PS_META_REPLACE, "Time system", PS_TIME_TAI);
+        }
+    }
+
+    return true;
+}
+
+// Copy concepts from one FPA to another
+bool pmFPACopyConcepts(pmFPA *target,   // The target FPA
+                       pmFPA *source    // The target FPA
+                      )
+{
+    // Copy FPA concepts
+    target->concepts = pap_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 (%d) and source (%d) 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 = pap_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 (%d) and source (%d) 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 = pap_psMetadataCopy(targetCell->concepts, sourceCell->concepts);
+        }
+    }
+
+    return true;
+}
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAConceptsGet.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAConceptsGet.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAConceptsGet.h	(revision 6961)
@@ -0,0 +1,103 @@
+#ifndef PM_FPA_CONCEPTS_GET_H
+#define PM_FPA_CONCEPTS_GET_H
+
+#include "pslib.h"
+#include "pmAstrometry.h"
+#include "psAdditionals.h"
+
+psMetadataItem *p_pmFPAConceptGetFromCamera(pmCell *cell, // The cell
+        const char *concept // Name of concept
+                                           );
+psMetadataItem *p_pmFPAConceptGetFromHeader(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 *p_pmFPAConceptGetFromDefault(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 *p_pmFPAConceptGetFromDB(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 *p_pmFPAConceptGet(pmFPA *fpa, // The FPA
+                                  pmChip *chip,// The chip
+                                  pmCell *cell, // The cell
+                                  psDB *db, // DB handle
+                                  const char *concept // Concept name
+                                 );
+void p_pmFPAConceptGetF32(pmFPA *fpa,   // The FPA
+                          pmChip *chip, // The chip
+                          pmCell *cell, // The cell
+                          psDB *db,     // DB handle
+                          psMetadata *concepts, // The concepts MD
+                          const char *name, // Name of the concept
+                          const char *comment // Comment for concept
+                         );
+void p_pmFPAConceptGetF64(pmFPA *fpa,   // The FPA
+                          pmChip *chip, // The chip
+                          pmCell *cell, // The cell
+                          psDB *db,     // DB handle
+                          psMetadata *concepts, // The concepts MD
+                          const char *name, // Name of the concept
+                          const char *comment // Comment for concept
+                         );
+void p_pmFPAConceptGetS32(pmFPA *fpa,   // The FPA
+                          pmChip *chip, // The chip
+                          pmCell *cell, // The cell
+                          psDB *db,     // DB handle
+                          psMetadata *concepts, // The concepts MD
+                          const char *name, // Name of the concept
+                          const char *comment // Comment for concept
+                         );
+void p_pmFPAConceptGetString(pmFPA *fpa, // The FPA
+                             pmChip *chip, // The chip
+                             pmCell *cell, // The cell
+                             psDB *db,  // DB handle
+                             psMetadata *concepts, // The concepts MD
+                             const char *name, // Name of the concept
+                             const char *comment // Comment for concept
+                            );
+
+
+// Ingest a single cell concept; this is the workhorse for ingesting cell concepts
+bool p_pmCellIngestConcept(pmFPA *fpa,  // The FPA
+                           pmChip *chip, // The chip
+                           pmCell *cell, // The cell
+                           psDB *db,    // DB handle
+                           const char *concept // Name of the concept
+                          );
+
+// Ingest concepts for the FPA
+void pmFPAIngestConcepts(pmFPA *fpa,    // The FPA
+                         psDB *db       // DB handle
+                        );
+// Ingest concepts for the chip
+bool pmChipIngestConcepts(pmChip *chip, // The chip
+                          psDB *db      // DB handle
+                         );
+// Ingest concepts for the cell
+bool pmCellIngestConcepts(pmCell *cell, // The cell
+                          psDB *db      // DB handle
+                         );
+
+// Set up concepts for blank image
+bool pmFPABlankConcepts(pmFPA *fpa      // The FPA
+                       );
+
+// Copy concepts from one FPA to another
+bool pmFPACopyConcepts(pmFPA *target,   // The target FPA
+                       pmFPA *source    // The target FPA
+                      );
+
+// Retrieve a concept
+psMetadataItem *pmCellGetConcept(pmCell *cell, // The cell
+                                 const char *concept // The concept
+                                );
+
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAConceptsSet.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAConceptsSet.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAConceptsSet.c	(revision 6961)
@@ -0,0 +1,904 @@
+#include <stdio.h>
+#include <strings.h>
+#include "pslib.h"
+
+#include "pmAstrometry.h"
+#include "pmFPAConceptsGet.h"
+#include "pmFPAConceptsSet.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#define COMPARE_REGIONS(a,b) (((a)->x0 == (b)->x0 && \
+                               (a)->x1 == (b)->x1 && \
+                               (a)->y0 == (b)->y0 && \
+                               (a)->y1 == (b)->y1) ? true : false)
+
+
+static bool compareConcepts(psMetadataItem *item1, // First item to compare
+                            psMetadataItem *item2 // Second item to compare
+                           )
+{
+    // First order checks
+    if (! item1 || ! item2) {
+        return false;
+    }
+    if (strcasecmp(item1->name, item2->name) != 0) {
+        return false;
+    }
+
+    // Check the more boring types
+    switch (item1->type) {
+    case PS_TYPE_S32:
+        switch (item2->type) {
+        case PS_TYPE_S32:
+            return (item1->data.S32 == item2->data.S32) ? true : false;
+        case PS_TYPE_F32:
+            return (item1->data.S32 == (int)item2->data.F32) ? true : false;
+        case PS_TYPE_F64:
+            return (item1->data.S32 == (int)item2->data.F64) ? true : false;
+        default:
+            return false;
+        }
+    case PS_TYPE_F32:
+        switch (item2->type) {
+        case PS_TYPE_S32:
+            return (item1->data.F32 == (float)item2->data.S32) ? true : false;
+        case PS_TYPE_F32:
+            return (item1->data.F32 == item2->data.F32) ? true : false;
+        case PS_TYPE_F64:
+            return (item1->data.F32 == (float)item2->data.F64) ? true : false;
+        default:
+            return false;
+        }
+    case PS_TYPE_F64:
+        switch (item2->type) {
+        case PS_TYPE_S32:
+            return (item1->data.F64 == (double)item2->data.S32) ? true : false;
+        case PS_TYPE_F32:
+            return (item1->data.F64 == (double)item2->data.F32) ? true : false;
+        case PS_TYPE_F64:
+            return (item1->data.F64 == item2->data.F64) ? true : false;
+        default:
+            return false;
+        }
+        return (item1->data.F64 == item2->data.F64) ? true : false;
+    case PS_DATA_STRING:
+        if (item2->type != PS_DATA_STRING) {
+            return false;
+        }
+        return (strcasecmp(item1->data.V, item2->data.V) == 0) ? true : false;
+    default:
+        return false;
+    }
+    psAbort(__func__, "Should never get here.\n");
+}
+
+
+// Well, not really "set", but check to make sure it's there and matches
+static bool setConceptInCamera(pmCell *cell, // The cell
+                               psMetadataItem *concept // Concept
+                              )
+{
+    if (cell) {
+        psMetadataItem *item = psMetadataLookup(cell->camera, concept->name); // Info we want
+        return compareConcepts(item, concept);
+    }
+
+    return false;
+}
+
+
+static bool setConceptInHeader(pmFPA *fpa, // The FPA that contains the chip
+                               pmChip *chip, // The chip that contains the cell
+                               pmCell *cell, // The cell
+                               psMetadataItem *concept // Concept
+                              )
+{
+    bool mdok = true;                   // Status of MD lookup
+    bool status = false;                // Status of setting header
+    psMetadata *translation = psMetadataLookupMD(&mdok, fpa->camera, "TRANSLATION"); // FITS translation
+    if (! mdok) {
+        psError(PS_ERR_IO, false, "Unable to find TRANSLATION in camera configuration.\n");
+        return false;
+    }
+
+    // Look for how to translate the concept into a FITS header name
+    const char *keyword = psMetadataLookupStr(&mdok, translation, concept->name);
+    if (mdok && strlen(keyword) > 0) {
+        psTrace(__func__, 6, "It's in keyword %s\n", keyword);
+        psMetadataItem *headerItem = NULL; // Item to add to header
+        // XXX: Need to expand range of types
+        switch (concept->type) {
+        case PS_DATA_STRING:
+            headerItem = psMetadataItemAllocStr(keyword, concept->comment, concept->data.V);
+            break;
+        case PS_DATA_S32:
+            headerItem = psMetadataItemAllocS32(keyword, concept->comment, concept->data.S32);
+            break;
+        case PS_DATA_F32:
+            headerItem = psMetadataItemAllocF32(keyword, concept->comment, concept->data.F32);
+            break;
+        case PS_DATA_F64:
+            headerItem = psMetadataItemAllocF64(keyword, concept->comment, concept->data.F64);
+            break;
+        default:
+            headerItem = psMetadataItemAlloc(keyword, concept->type, concept->comment,
+                                             concept->data.V); // Item for the header
+        }
+
+        // We have a FITS header to look up --- search each level
+        if (cell && cell->hdu) {
+            psTrace(__func__, 7, "Adding to the cell level header...\n");
+            psMetadataAddItem(cell->hdu->header, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
+            status = true;
+        } else if (chip && chip->hdu) {
+            psTrace(__func__, 7, "Adding to the chip level header...\n");
+            psMetadataAddItem(chip->hdu->header, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
+            status = true;
+        } else if (fpa->hdu) {
+            psTrace(__func__, 7, "Adding to the FPA level header...\n");
+            psMetadataAddItem(fpa->hdu->header, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
+            status = true;
+        } else {
+            // In desperation, add to the PHU --- it HAS to be in the header somewhere!
+            if (! fpa->phu) {
+                fpa->phu = psMetadataAlloc();
+            }
+            psTrace(__func__, 7, "Adding to the PHU...\n");
+            psMetadataAddItem(fpa->phu, headerItem, PS_LIST_TAIL, PS_META_REPLACE);
+            status = true;
+        }
+        psFree(headerItem);
+    }
+
+    // No header value
+    return status;
+}
+
+
+// Well, not really "set", but check to see if it's there, and matches
+static bool setConceptInDefault(pmFPA *fpa, // The FPA that contains the chip
+                                pmChip *chip, // The chip that contains the cell
+                                pmCell *cell, // The cell
+                                psMetadataItem *concept // Concept
+                               )
+{
+    bool mdOK = true;                   // Status of MD lookup
+    psMetadata *defaults = psMetadataLookupMD(&mdOK, fpa->camera, "DEFAULTS");
+    if (! mdOK || ! defaults) {
+        psError(PS_ERR_IO, false, "Unable to find DEFAULTS in camera configuration.\n");
+        return false;
+    }
+
+    psMetadataItem *defItem = psMetadataLookup(defaults, concept->name);
+    bool status = false;                // Result of checking the database
+    if (defItem) {
+        if (defItem->type == PS_DATA_METADATA) {
+            // A dependent default
+            psTrace(__func__, 7, "Evaluating dependent default....\n");
+            psMetadata *dependents = defItem->data.V; // The list of dependents
+            // Find out what it depends on
+            psString dependName = psStringCopy(concept->name);
+            psStringAppend(&dependName, ".DEPEND");
+            psString dependsOn = psMetadataLookupStr(&mdOK, defaults, dependName);
+            if (! mdOK) {
+                psError(PS_ERR_IO, false, "Unable to find %s in camera configuration for dependent default"
+                        " --- ignored\n", dependName);
+                // XXX: Need to clean up before returning
+                return false;
+            }
+            psFree(dependName);
+            // Find the value of the dependent concept
+            psMetadataItem *depItem = p_pmFPAConceptGetFromHeader(fpa, chip, cell, dependsOn);
+            if (! depItem) {
+                psError(PS_ERR_IO, true, "Unable to find value for %s (required for %s)\n", dependsOn,
+                        concept->name);
+                return false;
+            }
+            if (depItem->type != PS_DATA_STRING) {
+                psError(PS_ERR_IO, true, "Value of %s is not of type string, as required for dependency"
+                        " --- ignored.\n", dependsOn);
+            }
+
+            defItem = psMetadataLookup(dependents, depItem->data.V); // This is now what we were after
+        }
+
+        status = compareConcepts(defItem, concept);
+        if (! status) {
+            psError(PS_ERR_IO, true, "Concept %s is specified by default in the camera configuration, "
+                    "but doesn't match the actual value.\n", concept->name);
+        }
+    }
+
+    // XXX: Need to clean up before returning
+    return status;
+}
+
+
+// XXX: Not tested at all
+static bool setConceptInDB(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
+                          )
+{
+    if (! db) {
+        // No database initialised
+        return false;
+    }
+
+    bool mdStatus = true;               // Status of MD lookup
+    psMetadata *database = psMetadataLookupMD(&mdStatus, fpa->camera, "DATABASE");
+    if (! mdStatus) {
+        // No error, because not everyone needs to use the DB
+        return NULL;
+    }
+
+    psMetadata *dbLookup = psMetadataLookupMD(&mdStatus, database, concept->name);
+    if (dbLookup) {
+        const char *tableName = psMetadataLookupStr(&mdStatus, dbLookup, "TABLE"); // Name of the table
+        //        const char *colName = psMetadataLookupStr(&mdStatus, dbLookup, "COLUMN"); // Name of the column
+        const char *givenCols = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENDBCOL"); // Name of "where"
+        // columns
+        const char *givenPS = psMetadataLookupStr(&mdStatus, dbLookup, "GIVENPS"); // Values for "where"
+        // columns
+
+        // Now, need to get the "given"s
+        if (strlen(givenCols) || strlen(givenPS)) {
+            psList *cols = psStringSplit(givenCols, ",;"); // List of column names
+            psList *values = psStringSplit(givenPS, ",;"); // 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", concept);
+            } 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 *name = 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", concept);
+                    } else {
+                        // Search for the value name
+                        psMetadataItem *item = p_pmFPAConceptGetFromHeader(fpa, chip, cell, name);
+                        if (! item) {
+                            item = p_pmFPAConceptGetFromDefault(fpa, chip, cell, name);
+                        }
+                        if (! item) {
+                            psLogMsg(__func__, PS_LOG_ERROR, "Unable to find the value name %s for DB "
+                                     " lookup on %s --- ignored.\n", name, concept);
+                        } 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(concept->name, item->type,
+                                                      item->comment, item->data.V);
+                            psMetadataAddItem(selection, newItem, PS_LIST_TAIL, PS_META_REPLACE);
+                            psFree(newItem);
+                        }
+                    }
+                    psFree(name);
+                    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",
+                             concept->name);
+                    return false;
+                } else {
+                    if (dbResult->n > 1) {
+                        psLogMsg(__func__, PS_LOG_WARN, "Multiple rows returned in DB lookup for %s --- "
+                                 " ignored.\n", concept->name);
+                    }
+                    // Update the DB
+                    psMetadata *update = psMetadataAlloc();
+                    psMetadataAddItem(update, concept, PS_LIST_HEAD, 0);
+                    psDBUpdateRows(db, tableName, selection, update);
+                    psFree(update);
+                    return true;
+                }
+            }
+            psFree(cols);
+            psFree(values);
+        }
+    } // Doing the "given"s.
+
+    psAbort(__func__, "Shouldn't ever get here?\n");
+    return false;
+}
+
+
+// Concept set from item
+static bool setConceptItem(pmFPA *fpa, // The FPA
+                           pmChip *chip,// The chip
+                           pmCell *cell,        // The cell
+                           psDB *db, // DB handle
+                           psMetadataItem *concept // Concept item
+                          )
+{
+    // Try headers, database, defaults in order
+    psTrace(__func__, 3, "Trying to set concept %s...\n", concept->name);
+    bool status = setConceptInCamera(cell, concept); // Status for return
+    if (! status) {
+        psTrace(__func__, 5, "Trying header....\n");
+        status = setConceptInHeader(fpa, chip, cell, concept);
+    }
+    if (! status) {
+        psTrace(__func__, 5, "Trying database....\n");
+        status = setConceptInDB(fpa, chip, cell, db, concept);
+    }
+    if (! status) {
+        psTrace(__func__, 5, "Checking defaults....\n");
+        status = setConceptInDefault(fpa, chip, cell, concept);
+    }
+
+    if (! status) {
+        psError(PS_ERR_IO, true, "Unable to set %s (%s).\n", concept->name, concept->comment);
+    }
+
+    return status;
+}
+
+
+// Concept set
+static bool setConcept(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
+                      )
+{
+    psMetadataItem *concept = psMetadataLookup(concepts, name);
+    if (! concept) {
+        psError(PS_ERR_IO, true, "No such concept as %s\n", name);
+        return false;
+    }
+    return setConceptItem(fpa, chip, cell, db, concept);
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Outget concepts from the FPA
+void pmFPAOutgestConcepts(pmFPA *fpa,   // The FPA
+                          psDB *db      // DB handle
+                         )
+{
+    // FPA.NAME
+    setConcept(fpa, NULL, NULL, db, fpa->concepts, "FPA.NAME");
+
+    // FPA.AIRMASS
+    setConcept(fpa, NULL, NULL, db, fpa->concepts, "FPA.AIRMASS");
+
+    // FPA.FILTER
+    setConcept(fpa, NULL, NULL, db, fpa->concepts, "FPA.FILTER");
+
+    // FPA.POSANGLE
+    setConcept(fpa, NULL, NULL, db, fpa->concepts, "FPA.POSANGLE");
+
+    // FPA.RADECSYS
+    setConcept(fpa, NULL, NULL, db, fpa->concepts, "FPA.RADECSYS");
+
+    // These take some extra work
+
+    // FPA.RA
+    {
+        double ra = psMetadataLookupF64(NULL, fpa->concepts, "FPA.RA"); // The RA
+
+        // How to interpret the RA
+        const psMetadata *camera = fpa->camera; // Camera configuration data
+        bool mdok = true;               // Status of MD lookup
+        psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+        if (mdok && formats)
+        {
+            psString raFormat = psMetadataLookupStr(&mdok, formats, "FPA.RA");
+            if (mdok && strlen(raFormat) > 0) {
+                if (strcasecmp(raFormat, "HOURS") == 0) {
+                    ra /= M_PI / 12.0;
+                } else if (strcasecmp(raFormat, "DEGREES") == 0) {
+                    ra /= M_PI / 180.0;
+                } else if (strcasecmp(raFormat, "RADIANS") == 0) {
+                    // No action required
+                } else {
+                    psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.RA in FORMATS (%s) --- assuming"
+                             " HOURS.\n");
+                    ra /= M_PI / 12.0;
+                }
+            } else {
+                psError(PS_ERR_IO, false, "Unable to find FPA.RA in FORMATS --- assuming HOURS.\n");
+                ra /= M_PI / 12.0;
+            }
+        } else
+        {
+            psError(PS_ERR_IO, false, "Unable to find FORMAT metadata in camera configuration --- "
+                    "assuming format for FPA.RA is HOURS.\n");
+            ra /= M_PI / 12.0;
+        }
+
+        // We choose to write sexagesimal format
+        int big, medium;
+        float small;
+        big = (int)ra;
+        medium = (int)(60.0*(ra - (double)big));
+        small = 3600.0*(ra - (double)big - 60.0 * (double)medium);
+        psString raString = psStringCopy("");
+        psStringAppend(&raString, "%d:%d:%.2f", big, medium, small);
+        psMetadataItem *raItem = psMetadataItemAllocStr("FPA.RA", "Right Ascension of the boresight",
+                                 raString);
+        setConceptItem(fpa, NULL, NULL, db, raItem);
+        psFree(raItem);
+        psFree(raString);
+    }
+
+    // FPA.DEC
+    {
+        double dec = psMetadataLookupF64(NULL, fpa->concepts, "FPA.DEC"); // The DEC
+
+        // How to interpret the DEC
+        const psMetadata *camera = fpa->camera; // Camera configuration data
+        bool mdok = true;               // Status of MD lookup
+        psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+        if (mdok && formats)
+        {
+            psString decFormat = psMetadataLookupStr(&mdok, formats, "FPA.DEC");
+            if (mdok && strlen(decFormat) > 0) {
+                if (strcasecmp(decFormat, "HOURS") == 0) {
+                    dec /= M_PI / 12.0;
+                } else if (strcasecmp(decFormat, "DEGREES") == 0) {
+                    dec /= M_PI / 180.0;
+                } else if (strcasecmp(decFormat, "RADIANS") == 0) {
+                    // No action required
+                } else {
+                    psLogMsg(__func__, PS_LOG_WARN, "Don't understand FPA.DEC in FORMATS (%s) --- assuming"
+                             " DEGREES.\n");
+                    dec /= M_PI / 180.0;
+                }
+            } else {
+                psError(PS_ERR_IO, false, "Unable to find FPA.DEC in FORMATS --- assuming DEGREES.\n");
+                dec /= M_PI / 180.0;
+            }
+        } else
+        {
+            psError(PS_ERR_IO, false, "Unable to find FORMAT metadata in camera configuration --- "
+                    "assuming format for FPA.DEC is HOURS.\n");
+            dec /= M_PI / 12.0;
+        }
+
+        // We choose to write sexagesimal format
+        int big, medium;
+        float small;
+        big = (int)dec;
+        medium = (int)(60.0*(dec - (double)big));
+        small = 3600.0*(dec - (double)big - 60.0 * (double)medium);
+        psString decString = psStringCopy("");
+        psStringAppend(&decString, "%d:%d:%.2f", big, medium, small);
+        psMetadataItem *decItem = psMetadataItemAllocStr("FPA.DEC", "Right Ascension of the boresight",
+                                  decString);
+        setConceptItem(fpa, NULL, NULL, db, decItem);
+        psFree(decItem);
+        psFree(decString);
+
+    }
+
+    // Pau.
+}
+
+
+// Outgest concepts for the chip
+void pmChipOutgestConcepts(pmChip *chip, // The chip
+                           psDB *db     // DB handle
+                          )
+{
+    //    pmFPA *fpa = chip->parent;          // The parent FPA
+
+    // CHIP.NAME --- no need to do anything
+
+    // Pau.
+}
+
+
+// Outgest concepts for the cell
+void pmCellOutgestConcepts(pmCell *cell, // The cell
+                           psDB *db     // DB handle
+                          )
+{
+    pmChip *chip = cell->parent;        // The parent chip
+    pmFPA *fpa = chip->parent;          // The parent FPA
+
+    // CELL.NAME --- no need to do anything
+
+    // CELL.GAIN
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.GAIN");
+
+    // CELL.READNOISE
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.READNOISE");
+
+    // CELL.SATURATION
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.SATURATION");
+
+    // CELL.BAD
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.BAD");
+
+    // CELL.XPARITY
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.XPARITY");
+
+    // CELL.YPARITY
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.YPARITY");
+
+    // CELL.READDIR
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.READDIR");
+
+    // These used to be pmReadoutGetExposure and pmReadoutGetDarkTime, 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 (used to be READOUT.EXPOSURE)
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.EXPOSURE");
+
+    // CELL.DARKTIME (used to be READOUT.DARKTIME)
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.DARKTIME");
+
+    // These take some extra work
+
+    // CELL.TRIMSEC
+    {
+        psMetadataItem *trimsecItem = psMetadataLookup(cell->concepts, "CELL.TRIMSEC");
+        psRegion *trimsec = trimsecItem->data.V; // The trimsec region
+        psMetadataItem *sourceItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.TRIMSEC.SOURCE");
+        if (! sourceItem)
+        {
+            psError(PS_ERR_IO, false, "Couldn't find CELL.TRIMSEC.SOURCE.\n");
+        } else if (sourceItem->type != PS_DATA_STRING)
+        {
+            psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
+        } else
+        {
+            psString source = sourceItem->data.V; // The source string
+
+            if (strcasecmp(source, "VALUE") == 0) {
+                // Check that it's the same value as stored in the camera
+                psString checkString = psMetadataLookupStr(NULL, cell->camera, "CELL.TRIMSEC");
+                psRegion checkRegion = psRegionFromString(checkString);
+                if (! COMPARE_REGIONS(&checkRegion, trimsec)) {
+                    psError(PS_ERR_IO, true, "Target CELL.TRIMSEC is specified by value, and values don't "
+                            "match.\n");
+                }
+            } else if (strcasecmp(source, "HEADER") == 0) {
+                //                psString keyword = psMetadataLookupStr(NULL, cell->camera, "CELL.TRIMSEC");
+                psMetadata *header = NULL; // The FITS header
+                if (cell->hdu) {
+                    header = cell->hdu->header;
+                } else if (chip->hdu) {
+                    header = chip->hdu->header;
+                } else if (fpa->hdu) {
+                    header = fpa->hdu->header;
+                }
+                if (! header) {
+                    psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+                } else {
+                    psMetadataAddItem(header, trimsecItem, PS_LIST_TAIL, PS_META_REPLACE);
+                }
+            } else {
+                psError(PS_ERR_IO, true, "CELL.TRIMSEC.SOURCE (%s) is not HEADER or VALUE.\n", source);
+            }
+        }
+    }
+
+    // CELL.BIASSEC
+    {
+        psMetadataItem *biassecItem = psMetadataLookup(cell->concepts, "CELL.BIASSEC");
+        psList *biassecs = biassecItem->data.V; // The biassecs region list
+        psMetadataItem *sourceItem = p_pmFPAConceptGet(fpa, chip, cell, db, "CELL.BIASSEC.SOURCE");
+        if (! sourceItem)
+        {
+            psError(PS_ERR_IO, false, "Couldn't find CELL.BIASSEC.SOURCE.\n");
+        } else if (sourceItem->type != PS_DATA_STRING)
+        {
+            psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE is not of type STR (%x)\n", sourceItem->type);
+        } else
+        {
+            psString source = sourceItem->data.V; // The source string
+
+            if (strcasecmp(source, "VALUE") == 0) {
+                // Check that it's the same value as stored in the camera
+                psString checkString = psMetadataLookupStr(NULL, cell->camera, "CELL.BIASSEC");
+                psList *checkList = psStringSplit(checkString, " ;");
+                if (biassecs->n != checkList->n) {
+                    psError(PS_ERR_IO, true, "Target CELL.BIASSEC is specified by value, but number of "
+                            "entries doesn't match.\n");
+                } else {
+                    // We don't care if the order matches or not
+                    psVector *check = psVectorAlloc(biassecs->n, PS_TYPE_U8); // Vector to mark regions off
+                    for (int i = 0; i < check->n; i++) {
+                        check->data.U8[i] = 0;
+                    }
+                    psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD,
+                                                   false); // Iterator
+                    psListIterator *checkListIter = psListIteratorAlloc(checkList, PS_LIST_HEAD, false);
+                    psRegion *biassec = NULL; // Region from iteration
+                    while ((biassec = psListGetAndIncrement(biassecsIter))) {
+                        psListIteratorSet(checkListIter, PS_LIST_HEAD);
+                        psString checkRegionString = NULL; // Region string from iteration
+                        int i = 0;              // Counter
+                        while ((checkRegionString = psListGetAndIncrement(checkListIter))) {
+                            psRegion checkRegion = psRegionFromString(checkRegionString);
+                            psTrace(__func__, 7, "Checking [%.0f:%.0f,%.0f:%.0f] against "
+                                    "[%.0f:%.0f,%.0f:%.0f]\n", biassec->x0, biassec->x1, biassec->y0,
+                                    biassec->y1, checkRegion.x0, checkRegion.x1, checkRegion.y0,
+                                    checkRegion.y1);
+                            if (COMPARE_REGIONS(biassec, &checkRegion)) {
+                                check->data.U8[i] = 1;
+                                i++;
+                                break;
+                            }
+                            i++;
+                        }
+                    }
+                    for (int i = 0; i < check->n; i++) {
+                        if (check->data.U8[i] == 0) {
+                            psError(PS_ERR_IO, true, "Target CELL.BIASSEC is specified by value, but values "
+                                    "don't match.\n");
+                            break;
+                        }
+                    }
+                    psFree(checkListIter);
+                    psFree(biassecsIter);
+                    psFree(check);
+                }
+                psFree(checkList);
+            } else if (strcasecmp(source, "HEADER") == 0) {
+                psString keywordsString = psMetadataLookupStr(NULL, cell->camera, "CELL.BIASSEC");
+                psList *keywords = psStringSplit(keywordsString, " ,;");
+                if (biassecs->n != keywords->n) {
+                    psError(PS_ERR_IO, true, "Target CELL.BIASSEC is sepcified by headers, but the number "
+                            "of headers doesn't match.\n");
+                } else {
+                    psMetadata *header = NULL; // The FITS header
+                    if (cell->hdu) {
+                        header = cell->hdu->header;
+                    } else if (chip->hdu) {
+                        header = chip->hdu->header;
+                    } else if (fpa->hdu) {
+                        header = fpa->hdu->header;
+                    }
+                    if (! header) {
+                        psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+                    } else {
+                        psListIterator *keywordsIter = psListIteratorAlloc(keywords, PS_LIST_HEAD, false);
+                        psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false);
+                        psString keyword = NULL; // Header keyword from list
+                        while ((keyword = psListGetAndIncrement(keywordsIter))) {
+                            // Update the header
+                            psRegion *biassec = psListGetAndIncrement(biassecsIter);
+                            psString biassecString = psRegionToString(*biassec);
+                            psMetadataAdd(header, PS_LIST_TAIL, keyword, PS_DATA_STRING | PS_META_REPLACE,
+                                          "Bias section", biassecString);
+                            psFree(biassecString);
+                        }
+                        psFree(keywordsIter);
+                        psFree(biassecsIter);
+                    }
+                }
+                psFree(keywords);
+            } else {
+                psError(PS_ERR_IO, true, "CELL.BIASSEC.SOURCE (%s) is not HEADER or VALUE.\n", source);
+            }
+        }
+    }
+
+    // CELL.XBIN and CELL.YBIN
+    {
+        psMetadataItem *xBinItem = psMetadataLookup(cell->concepts, "CELL.XBIN"); // Binning factor in x
+        psMetadataItem *yBinItem = psMetadataLookup(cell->concepts, "CELL.YBIN"); // Binning factor in y
+
+        // If these are specified by the header, we need to check to see if it's the same header keyword
+        const psMetadata *camera = fpa->camera;
+        psMetadata *translation = psMetadataLookupMD(NULL, camera, "TRANSLATION");
+        psString xKeyword = psMetadataLookupStr(NULL, translation, "CELL.XBIN");
+        psString yKeyword = psMetadataLookupStr(NULL, translation, "CELL.YBIN");
+        if (strlen(xKeyword) > 0 && strcasecmp(xKeyword, yKeyword) != 0)
+        {
+            setConceptInHeader(fpa, chip, cell, xBinItem);
+            xBinItem = NULL;
+        }
+        if (strlen(yKeyword) > 0 && strcasecmp(xKeyword, yKeyword) != 0)
+        {
+            setConceptInHeader(fpa, chip, cell, yBinItem);
+            yBinItem = NULL;
+        }
+        if (strlen(xKeyword) > 0 && strlen(yKeyword) > 0 && strcasecmp(xKeyword, yKeyword) == 0)
+        {
+            psString binString = psStringCopy("");
+            psStringAppend(&binString, "%d,%d", xBinItem->data.S32, yBinItem->data.S32);
+            psMetadataItem *binItem = psMetadataItemAllocStr(xKeyword, "Binning factor in x and y",
+                                      binString);
+            psFree(binString);
+            psMetadata *header = NULL; // The FITS header
+            if (cell->hdu) {
+                header = cell->hdu->header;
+            } else if (chip->hdu) {
+                header = chip->hdu->header;
+            } else if (fpa->hdu) {
+                header = fpa->hdu->header;
+            }
+            if (! header) {
+                psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+            } else {
+                psMetadataAddItem(header, binItem, PS_LIST_TAIL, PS_META_REPLACE);
+                xBinItem = NULL;
+                yBinItem = NULL;
+            }
+            psFree(binItem);
+        }
+
+        // Do it in the usual way if we have to
+        if (xBinItem)
+        {
+            setConceptItem(fpa, chip, cell, db, xBinItem);
+        }
+        if (yBinItem)
+        {
+            setConceptItem(fpa, chip, cell, db, yBinItem);
+        }
+    }
+
+    // CELL.TIMESYS
+    {
+        psMetadataItem *sysItem = psMetadataLookup(cell->concepts, "CELL.TIMESYS");
+        psString sys = NULL;            // String to store
+        switch (sysItem->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("CELL.TIMESYS", "Time system", sys);
+        setConceptItem(fpa, chip, cell, db, newItem);
+        psFree(newItem);
+        psFree(sys);
+    }
+
+    // CELL.TIME
+    {
+        psMetadataItem *timeItem = psMetadataLookup(cell->concepts, "CELL.TIME");
+        psTime *time = timeItem->data.V; // The time
+        psString dateTimeString = psTimeToISO(time); // String representation
+
+        psMetadata *formats = psMetadataLookupMD(NULL, fpa->camera, "FORMATS");
+        psString format = psMetadataLookupStr(NULL, formats, "CELL.TIME");
+
+        if (strlen(format) == 0)
+        {
+            // No format specified --> do it in the usual way (maybe it's a DB lookup)
+            psMetadataItem *newTimeItem = psMetadataItemAllocStr("CELL.TIME", "Time of observation",
+                                          dateTimeString);
+            setConceptItem(fpa, chip, cell, db, newTimeItem);
+            psFree(newTimeItem);
+        } else
+        {
+            if (strcasecmp(format, "ISO") == 0) {
+                // dateTimeString contains an ISO time
+                psMetadataItem *newTimeItem = psMetadataItemAllocStr("CELL.TIME", "Time of observation",
+                                              dateTimeString);
+                setConceptItem(fpa, chip, cell, db, newTimeItem);
+                psFree(newTimeItem);
+            } else if (strstr(format, "SEPARATE")) {
+                // We're working with two separate headers
+                psMetadata *header = NULL; // The FITS header
+                if (cell->hdu) {
+                    header = cell->hdu->header;
+                } else if (chip->hdu) {
+                    header = chip->hdu->header;
+                } else if (fpa->hdu) {
+                    header = fpa->hdu->header;
+                }
+                if (! header) {
+                    psError(PS_ERR_IO, true, "Unable to find FITS header!\n");
+                } else {
+                    // Get the headers
+                    const psMetadata *camera = fpa->camera;
+                    psMetadata *translation = psMetadataLookupMD(NULL, camera, "TRANSLATION");
+                    psString keywords = psMetadataLookupStr(NULL, translation, "CELL.TIME");
+                    psList *dateTimeKeywords = psStringSplit(keywords, " ,;");
+                    psString dateKeyword = psListGet(dateTimeKeywords, PS_LIST_HEAD);
+                    psString timeKeyword = psListGet(dateTimeKeywords, PS_LIST_TAIL);
+
+                    psList *dateTime = psStringSplit(dateTimeString, " T"); // Find the middle T
+                    psString dateString = psListGet(dateTime, PS_LIST_HEAD);
+                    psString timeString = psListGet(dateTime, PS_LIST_TAIL);
+
+                    // XXX: Couldn't be bothered doing these right now
+                    if (strstr(format, "PRE2000")) {
+                        psError(PS_ERR_IO, true, "Don't you realise it's the twenty-first century?\n");
+                    }
+                    if (strstr(format, "BACKWARDS")) {
+                        psError(PS_ERR_IO, true, "You want it BACKWARDS?  Not right now, thanks.\n");
+                    }
+
+                    psMetadataItem *dateItem = psMetadataItemAllocStr(dateKeyword, "Date of observation",
+                                               dateString);
+                    psMetadataItem *timeItem = psMetadataItemAllocStr(timeKeyword, "Time of observation",
+                                               timeString);
+                    psMetadataAddItem(header, dateItem, PS_LIST_TAIL, PS_META_REPLACE);
+                    psMetadataAddItem(header, timeItem, PS_LIST_TAIL, PS_META_REPLACE);
+
+                    psFree(dateTimeKeywords);
+                    psFree(dateTime);
+                    psFree(dateItem);
+                    psFree(timeItem);
+                }
+            } else if (strcasecmp(format, "MJD") == 0) {
+                double mjd = psTimeToMJD(time);
+                psMetadataItem *newTimeItem = psMetadataItemAllocF64("CELL.TIME", "MJD of observation", mjd);
+                setConceptItem(fpa, chip, cell, db, newTimeItem);
+                psFree(newTimeItem);
+            } else if (strcasecmp(format, "JD") == 0) {
+                double jd = psTimeToMJD(time);
+                psMetadataItem *newTimeItem = psMetadataItemAllocF64("CELL.TIME", "JD of observation", jd);
+                setConceptItem(fpa, chip, cell, db, newTimeItem);
+                psFree(newTimeItem);
+            } else {
+                psError(PS_ERR_IO, true, "Not sure how to outgest CELL.TIME (%s) --- trying "
+                        "ISO\n", dateTimeString);
+                psMetadataItem *newTimeItem = psMetadataItemAllocStr("CELL.TIME", "Time of observation",
+                                              dateTimeString);
+                setConceptItem(fpa, chip, cell, db, newTimeItem);
+                psFree(newTimeItem);
+            }
+        }
+
+        psFree(dateTimeString);
+    }
+
+    // Remove corrective
+    {
+        const psMetadata *camera = fpa->camera;
+        bool mdok = false;              // Result of MD lookup
+        psMetadata *formats = psMetadataLookupMD(&mdok, camera, "FORMATS");
+        if (mdok && formats)
+        {
+            psString format = psMetadataLookupStr(&mdok, formats, "CELL.X0");
+            if (mdok && strlen(format) > 0 && strcasecmp(format, "FORTRAN") == 0) {
+                psMetadataItem *cellx0 = psMetadataLookup(cell->concepts, "CELL.X0");
+                cellx0->data.S32 += 1;
+            }
+            format = psMetadataLookupStr(&mdok, formats, "CELL.Y0");
+            if (mdok && strlen(format) > 0 && strcasecmp(format, "FORTRAN") == 0) {
+                psMetadataItem *celly0 = psMetadataLookup(cell->concepts, "CELL.Y0");
+                celly0->data.S32 += 1;
+            }
+        }
+    }
+    // CELL.X0
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.X0");
+    // CELL.Y0
+    setConcept(fpa, chip, cell, db, cell->concepts, "CELL.Y0");
+
+    // Pau.
+}
+
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAConceptsSet.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAConceptsSet.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAConceptsSet.h	(revision 6961)
@@ -0,0 +1,20 @@
+#ifndef PM_FPA_CONCEPTS_SET_H
+#define PM_FPA_CONCEPTS_SET_H
+
+#include "pslib.h"
+#include "pmAstrometry.h"
+
+void pmFPAOutgestConcepts(pmFPA *fpa,   // The FPA
+                          psDB *db      // DB handle
+                         );
+void pmChipOutgestConcepts(pmChip *chip,        // The chip
+                           psDB *db     // DB handle
+                          );
+void pmCellOutgestConcepts(pmCell *cell, // The cell
+                           psDB *db     // DB handle
+                          );
+
+
+
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAConstruct.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAConstruct.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAConstruct.c	(revision 6961)
@@ -0,0 +1,779 @@
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+#include "pslib.h"
+#include "psMetadataItemParse.h"
+
+#include "pmFPA.h"
+#include "pmConcepts.h"
+#include "pmFPAConstruct.h"
+#include "pmFPAview.h"
+#include "pmFPAUtils.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
+                              )
+{
+    bool status = true;                 // Result of MD lookup
+    psMetadata *cells = psMetadataLookupMD(&status, format, "CELLS"); // The CELLS
+    if (! status) {
+        psError(PS_ERR_IO, false, "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
+                        )
+{
+    bool allOK = true;                  // Everything was OK?
+    psList *values = psStringSplit(string, " ,;"); // 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, ":"); // 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
+                                 )
+{
+    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, false, "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);
+    if (hdu->header) {
+        pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER, NULL);
+    }
+    pmFPASetFileStatus(fpa, true);
+
+    return true;
+}
+
+// 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);
+    if (hdu->header) {
+        pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER, true, NULL);
+    }
+    pmChipSetFileStatus(chip, true);
+
+    return true;
+}
+
+// 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);
+    if (hdu->header) {
+        pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, true, NULL);
+    }
+    pmCellSetFileStatus(cell, true);
+
+    return true;
+}
+
+
+// Looks up the particular content based on the header
+static const char *getContent(psMetadata *fileInfo, // The FILE from the camera format configuration
+                              psMetadata *contents, // The CONTENTS from the camera format configuration
+                              psMetadata *header // The (primary) header
+                             )
+{
+    bool mdok = true;                   // Status of MD lookup
+    const char *contentHeaders = psMetadataLookupStr(&mdok, fileInfo, "CONTENT"); // Headers for content
+    if (!mdok || !contentHeaders || strlen(contentHeaders) == 0) {
+        psError(PS_ERR_IO, true, "Unable to find CONTENT in FILE within camera format configuration.\n");
+        return NULL;
+    }
+
+    psList *keywords = psStringSplit(contentHeaders, " ,;"); // List of keywords
+    psListIterator *keywordsIter = psListIteratorAlloc(keywords, PS_LIST_HEAD, false); // Iterator
+    psString keyword = NULL;        // Keyword, from iteration
+    psString contentsKey = NULL;    // Key to the CONTENTS menu
+    bool first = true;              // Is it the first value?
+    while ((keyword = psListGetAndIncrement(keywordsIter))) {
+        const char *value = psMetadataLookupStr(&mdok, header, keyword);
+        if (first) {
+            psStringAppend(&contentsKey, "%s", value);
+            first = false;
+        } else {
+            psStringAppend(&contentsKey, "_%s", value);
+        }
+    }
+    psFree(keywordsIter);
+    psFree(keywords);
+    psTrace(__func__, 5, "Looking up %s in the CONTENTS.\n", contentsKey);
+    const char *content = psMetadataLookupStr(&mdok, contents, contentsKey);
+    if (!mdok || !content || strlen(content) == 0) {
+        psError(PS_ERR_IO, true, "Unable to find %s in the CONTENTS.\n", contentsKey);
+        return NULL;
+    }
+    psFree(contentsKey);
+
+    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
+                           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
+
+    if (hdu && level == PM_FPA_LEVEL_FPA) {
+        addHDUtoFPA(fpa, hdu);
+    }
+
+    // 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;
+        }
+
+        // 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
+
+        // Put in the HDU
+        if (hdu && level == PM_FPA_LEVEL_CHIP) {
+            addHDUtoChip(newChip, hdu);
+        }
+        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);
+        pmConceptsReadCell(newCell, PM_CONCEPT_SOURCE_CAMERA | PM_CONCEPT_SOURCE_DEFAULTS, false, NULL);
+        numCells++;
+    }
+    psFree(chips);
+    psFree(cells);
+    psFree(types);
+
+    return numCells;
+}
+
+static pmFPALevel hduLevel(psMetadata *format // The camera format configuration
+                          )
+{
+    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
+                     )
+{
+    pmFPA *fpa = pmFPAAlloc(camera);    // The FPA to fill out
+
+    bool mdok = true;                   // Status from MD lookups
+    psMetadata *components = psMetadataLookupMD(&mdok, camera, "FPA"); // FPA components
+    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, " ,;"); // 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;
+}
+
+bool pmFPAAddSourceFromView(pmFPA *fpa,   // The FPA
+                            const pmFPAview *phuView, // The view, corresponding to the PHU
+                            psMetadata *format // Format of file
+                           )
+{
+    assert(fpa);
+    assert(phuView);
+    assert(format);
+
+    // Where does the PHU go?
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *fileInfo = psMetadataLookupMD(&mdok, format, "FILE"); // File information from the format
+    if (!mdok || !fileInfo) {
+        psError(PS_ERR_IO, false, "Unable to find FILE information in the camera format configuration.\n");
+        return false;
+    }
+    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 FILE information of the camera format.\n");
+        return false;
+    }
+
+    // Generate the PHU
+    pmHDU *phdu = pmHDUAlloc("PHU");    // The primary header data unit
+    phdu->format = psMemIncrRefCounter(format);
+
+    // Put in the PHU
+    pmChip *chip = NULL;                // The chip that corresponds to the PHU
+    pmCell *cell = NULL;                // The cell that corresponds to the PHU
+    if (strcasecmp(phuType, "FPA") == 0) {
+        addHDUtoFPA(fpa, phdu);
+        psFree(phdu);
+    } else {
+        psArray *chips = fpa->chips;    // Array of chips
+        if (phuView->chip < 0 || phuView->chip > chips->n) {
+            psError(PS_ERR_IO, true, "PHU specified by the camera format requires specification of a "
+                    "particular chip, which cannot be determined from the view (%d).\n", phuView->chip);
+            psFree(phdu);
+            return false;
+        }
+        chip = chips->data[phuView->chip];
+        if (strcasecmp(phuType, "CHIP") == 0) {
+            addHDUtoChip(chip, phdu);
+            psFree(phdu);
+        } else if (strcasecmp(phuType, "CELL") == 0) {
+            psArray *cells = chip->cells; // Array of cells
+            if (phuView->cell < 0 || phuView->cell > cells->n) {
+                psError(PS_ERR_IO, true, "PHU specified by the camera format requires specification of a "
+                        "particular cell, which cannot be determined from the view (%d).\n", phuView->cell);
+                psFree(phdu);
+                return false;
+            }
+            addHDUtoCell(cell, phdu);
+            psFree(phdu);
+        } else {
+            psError(PS_ERR_IO, true, "PHU in the camera configuration format is not FPA, CHIP or CELL.\n");
+            psFree(phdu);
+            return false;
+        }
+    }
+
+    // Put in the extensions
+    pmFPALevel level = hduLevel(format);// The level for the HDUs to go
+    if (level == PM_FPA_LEVEL_NONE) {
+        // No extensions --- we're done
+        return true;
+    }
+    psMetadata *contents = psMetadataLookupMD(&mdok, format, "CONTENTS"); // The contents of the FITS file
+    if (!mdok || !contents) {
+        psError(PS_ERR_IO, false, "Unable to find CONTENTS in the camera format configuration.\n");
+        return false;
+    }
+
+    psMetadataIterator *contentsIter = psMetadataIteratorAlloc(contents, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *contentsItem = NULL;// Item from the contents iteration
+    while ((contentsItem = psMetadataGetAndIncrement(contentsIter))) {
+        if (contentsItem->type != PS_DATA_STRING) {
+            psLogMsg(__func__, PS_LOG_WARN, "Content item %s is not of type STR --- ignored.\n",
+                     contentsItem->name);
+            continue;
+        }
+        pmHDU *hdu = pmHDUAlloc(contentsItem->name); // HDU to add
+        hdu->format = psMemIncrRefCounter(format);
+        const char *content = contentsItem->data.V; // The content data
+        processContents(fpa, chip, cell, hdu, level, content, format);
+        psFree(hdu);
+    }
+    psFree(contentsIter);
+
+    return true;
+}
+
+
+
+// Add an input file to the FPA
+pmFPAview *pmFPAAddSourceFromHeader(pmFPA *fpa, // The FPA
+                                    psMetadata *phu, // Primary header of file
+                                    psMetadata *format // Format of file
+                                   )
+{
+    assert(fpa);
+    assert(phu);
+    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;
+    }
+
+    // Check the name of the FPA
+    psString newFPAname = phuNameFromHeader("FPA.NAME", fileInfo, phu); // New name for the FPA
+    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
+
+    // Where 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;
+    }
+    pmHDU *phdu = pmHDUAlloc("PHU");    // The primary header data unit
+    phdu->header = psMemIncrRefCounter(phu);
+    phdu->format = psMemIncrRefCounter(format);
+    // Generate the view
+    pmFPAview *view = pmFPAviewAlloc(0); // The FPA view corresponding to the PHU, to be returned
+    view->chip = -1;
+    view->cell = -1;
+    view->readout = -1;
+
+    // And what are the individual extensions?
+    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;
+    }
+
+
+    // This is a special case: PHU=FPA and EXTENSIONS=NONE.  The only reason to do this is in the case of
+    // single CCD imagers, 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.
+    if (strcasecmp(phuType, "FPA") == 0 && strcasecmp(extType, "NONE") == 0) {
+        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(view);
+            return NULL;
+        }
+
+        processContents(fpa, NULL, NULL, phdu, PM_FPA_LEVEL_FPA, contents, format);
+        psFree(phdu);
+        return view;
+    }
+
+    // In all other cases, 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) {
+        psError(PS_ERR_IO, false, "Unable to find CONTENTS in the camera format configuration.\n");
+        psFree(view);
+        return NULL;
+    }
+
+    // No extensions --- only have the PHU.  In this case, the CONTENTS is a menu, with CONTENT indicating
+    // header keywords that provides a key to the CONTENTS menu.
+    if (strcasecmp(extType, "NONE") == 0) {
+        // We have already dealt with the case PHU=FPA, in a special case, above.
+        const char *content = getContent(fileInfo, contents, phu); // The content: string of chip:cell pairs
+
+        // Need to look up what chip we have.
+        psString chipName = phuNameFromHeader("CHIP.NAME", fileInfo, phu);
+        psTrace(__func__, 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(view);
+            return NULL;
+        }
+        pmChip *chip = fpa->chips->data[chipNum]; // Chip of interest
+        view->chip = chipNum;
+
+        pmCell *cell = NULL;            // Cell of interest
+
+        pmFPALevel level = PM_FPA_LEVEL_NONE; // Level for HDU to be added
+        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, phu);
+            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];
+            view->cell = cellNum;
+            psFree(cellName);
+        } else {
+            psError(PS_ERR_IO, true, "PHU is not FPA, CHIP or CELL.\n");
+            psFree(view);
+            return NULL;
+        }
+        psFree(chipName);
+
+        processContents(fpa, chip, cell, phdu, level, content, format);
+        psFree(phdu);
+        return view;
+    }
+
+
+    // From here on, we have extensions that we iterate through.  The CONTENTS is a list of extensions.
+    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, phu); // 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, phu); // 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
+        hdu->format = psMemIncrRefCounter(format);
+
+        processContents(fpa, chip, cell, hdu, level, contentsItem->data.V, format);
+        psFree(hdu);
+    }
+    psFree(contentsIter);
+
+    pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_DEFAULTS, NULL);
+
+    return view;
+}
+
+
+// Print out the focal plane structure
+void pmFPAPrint(pmFPA *fpa,             // FPA to print
+                bool header,            // Print headers?
+                bool concepts           // Print concepts?
+               )
+{
+    psTrace(__func__, 1, "FPA:\n");
+    if (fpa->hdu) {
+        psTrace(__func__, 2, "---> FPA is extension %s.\n", fpa->hdu->extname);
+        if (! fpa->hdu->images) {
+            psTrace(__func__, 2, "---> NO PIXELS read in for extension %s\n", fpa->hdu->extname);
+        }
+        if (header) {
+            if (fpa->hdu->header) {
+                psTrace(__func__, 2, "---> Header:\n");
+                psMetadataPrint(fpa->hdu->header, 8);
+            } else {
+                psTrace(__func__, 2, "---> NO HEADER read in for extension %s\n", fpa->hdu->extname);
+            }
+        }
+    }
+    if (concepts) {
+        psMetadataPrint(fpa->concepts, 2);
+    }
+
+    psArray *chips = fpa->chips;        // Array of chips
+    // Iterate over the FPA
+    for (int i = 0; i < chips->n; i++) {
+        psTrace(__func__, 3, "Chip: %d\n", i);
+        pmChip *chip = chips->data[i]; // The chip
+        if (chip->hdu) {
+            psTrace(__func__, 4, "---> Chip is extension %s.\n", chip->hdu->extname);
+            if (header) {
+                if (chip->hdu->header) {
+                    psTrace(__func__, 4, "---> Header:\n");
+                    psMetadataPrint(chip->hdu->header, 8);
+                } else {
+                    psTrace(__func__, 4, "---> NO HEADER read in for extension %s\n", chip->hdu->extname);
+                }
+            }
+            if (! chip->hdu->images) {
+                psTrace(__func__, 4, "---> NO PIXELS read in for extension %s\n", chip->hdu->extname);
+            }
+        }
+        if (concepts) {
+            psMetadataPrint(chip->concepts, 4);
+        }
+
+        // Iterate over the chip
+        psArray *cells = chip->cells;   // Array of cells
+        for (int j = 0; j < cells->n; j++) {
+            psTrace(__func__, 5, "Cell: %d\n", j);
+            pmCell *cell = cells->data[j]; // The cell
+            if (cell->hdu) {
+                psTrace(__func__, 6, "---> Cell is extension %s.\n", cell->hdu->extname);
+                if (header) {
+                    if (cell->hdu->header) {
+                        psTrace(__func__, 6, "---> Header:\n");
+                        psMetadataPrint(cell->hdu->header, 8);
+                    } else {
+                        psTrace(__func__, 6, "---> NO HEADER read in for extension %s\n", cell->hdu->extname);
+                    }
+                }
+                if (! cell->hdu->images) {
+                    psTrace(__func__, 6, "---> NO PIXELS read in for extension %s\n", cell->hdu->extname);
+                }
+            }
+            if (concepts) {
+                psMetadataPrint(cell->concepts, 6);
+            }
+
+            psTrace(__func__, 7, "Readouts:\n");
+            psArray *readouts = cell->readouts; // Array of readouts
+            for (int k = 0; k < readouts->n; k++) {
+                pmReadout *readout = readouts->data[k]; // The readout
+                psTrace(__func__, 8, "row0: %d\n", readout->row0);
+                psImage *image = readout->image; // The image
+                psList *bias = readout->bias; // The list of bias images
+                if (image) {
+                    psTrace(__func__, 8, "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(__func__, 8, "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/rel11_ifa_pre0/psModules/src/astrom/pmFPAConstruct.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAConstruct.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAConstruct.h	(revision 6961)
@@ -0,0 +1,28 @@
+#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
+                            psMetadata *format // Format of file
+                           );
+
+pmFPAview *pmFPAAddSourceFromHeader(pmFPA *fpa, // The FPA
+                                    psMetadata *phu, // Primary header of file
+                                    psMetadata *format // Format of file
+                                   );
+
+// Print out the FPA
+void pmFPAPrint(pmFPA *fpa,             // FPA to print
+                bool header,            // Print headers?
+                bool concepts           // Print concepts?
+               );
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPACopy.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPACopy.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPACopy.c	(revision 6961)
@@ -0,0 +1,693 @@
+#include <stdio.h>
+#include <assert.h>
+
+#include "pslib.h"
+#include "psImageFlip.h"
+#include "psRegionIsBad.h"
+
+#include "pmFPA.h"
+#include "pmFPAUtils.h"
+#include "pmHDU.h"
+#include "pmHDUUtils.h"
+#include "pmFPACopy.h"
+
+#define MAX(x,y) ((x) > (y) ? (x) : (y))
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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
+        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
+        result |= addCellsFromChip(list, chip);
+    }
+
+    return result;
+}
+
+// Get a list of cells that share the HDU for the target cell
+static bool cellList(psList *targets,   // The list of target cells
+                     psList *sources,   // The list of source cells
+                     pmCell *targetCell, // The target cell
+                     pmCell *sourceCell // The source cell
+                    )
+{
+    assert(targetCell);
+    assert(sourceCell);
+
+    if (targetCell->hdu) {
+        if (targets) {
+            psListAdd(targets, PS_LIST_TAIL, targetCell);
+        }
+        if (sources) {
+            psListAdd(sources, PS_LIST_TAIL, sourceCell);
+        }
+    } else {
+        pmChip *targetChip = targetCell->parent; // The target parent chip
+        pmChip *sourceChip = sourceCell->parent; // The source parent chip
+        if (targetChip->hdu) {
+            if (targets) {
+                addCellsFromChip(targets, targetChip);
+            }
+            if (sources) {
+                addCellsFromChip(sources, sourceChip);
+            }
+        } else {
+            pmFPA *targetFPA = targetChip->parent; // The target parent FPA
+            pmFPA *sourceFPA = sourceChip->parent; // The source parent FPA
+            if (targetFPA->hdu) {
+                if (targets) {
+                    addCellsFromFPA(targets, targetFPA);
+                }
+                if (sources) {
+                    addCellsFromFPA(sources, sourceFPA);
+                }
+            } else {
+                psError(PS_ERR_IO, true, "Unable to find HDU for cell to generate list!\n");
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+// 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 && !psRegionIsBad(*trimsec)) {
+            *xSize = MAX(trimsec->x1, *xSize);
+            *ySize = 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 (!psRegionIsBad(*trimsec)) {
+                    *xSize = MAX(biassec->x1, *xSize);
+                    *ySize = 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;
+    switch (readdir) {
+    case 1:                           // Read direction is rows
+        region = psRegionSet(*position, *position + image->numCols, image->row0,
+                             image->row0 + image->numRows);
+        *position += image->numCols;
+        break;
+    case 2:                           // Read direction is columns
+        region = psRegionSet(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
+                           pmCell *target, // Target cell
+                           const pmCell *source // Source cell
+                          )
+{
+    psMetadataItem *biassecItem = psMetadataLookup(target->concepts, "CELL.BIASSEC"); // Bias sections
+    if (!biassecItem) {
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.BIASSEC has not been initialised in target 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 readdir = psMetadataLookupS32(&mdok, source->concepts, "CELL.READDIR"); // Read direction
+    if (!mdok || (readdir != 1 && readdir != 2)) {
+        // Probably unnecessary, but just in case....
+        psLogMsg(__func__, PS_LOG_WARN, "CELL.READDIR is not set in source cell --- ignored.\n");
+        return false;
+    }
+
+    pmReadout *readout = source->readouts->data[0]; // The first source readout, as representative
+    psList *biases = readout->bias; // The bias images from the source 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 = psAlloc(sizeof(psRegion)); // The new region; need a psMemBlock
+        *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 target cells
+static bool generateTrimBias(psList *targets, // List of target cells
+                             psList *sources // List of source cells
+                            )
+{
+    pmCell *target = NULL, *source = NULL; // Cells from iteration
+    int numCells = targets->n;          // Number of cells
+    int cellNum = 0;                    // The cell number
+    int position = 0;                   // Position on the image
+    bool mdok = true;                   // Status of MD lookup
+
+    // First run through to do the LHS biases
+    psListIterator *targetsIter = psListIteratorAlloc(targets, PS_LIST_HEAD, false); // Iterator for targets
+    psListIterator *sourcesIter = psListIteratorAlloc(sources, PS_LIST_HEAD, false); // Iterator for sources
+    bool done = false;                   // Done with iteration?
+    while ((target = psListGetAndIncrement(targetsIter)) && (source = psListGetAndIncrement(sourcesIter)) &&
+            !done) {
+        if (cellNum <= numCells/2 - 1) {
+            doBiasSections(&position, target, source);
+            cellNum++;
+        } else {
+            done = true;
+        }
+    }
+
+    // Second run through to do the trim sections
+    psListIteratorSet(targetsIter, PS_LIST_HEAD);
+    psListIteratorSet(sourcesIter, PS_LIST_HEAD);
+    while ((target = psListGetAndIncrement(targetsIter)) && (source = psListGetAndIncrement(sourcesIter))) {
+        psRegion *trimsec = psMetadataLookupPtr(&mdok, target->concepts, "CELL.TRIMSEC"); // Trim section
+        if (!mdok || !trimsec) {
+            psLogMsg(__func__, PS_LOG_WARN, "CELL.TRIMSEC has not been initialised in target cell --- "
+                     "ignored.\n");
+            continue;
+        }
+
+        int readdir = psMetadataLookupS32(&mdok, source->concepts, "CELL.READDIR"); // Read direction
+        if (!mdok || (readdir != 1 && readdir != 2)) {
+            // Probably unnecessary, but just in case....
+            psLogMsg(__func__, PS_LOG_WARN, "CELL.READDIR is not set in source cell --- ignored.\n");
+            continue;
+        }
+
+        pmReadout *readout = source->readouts->data[0]; // The first source readout, as representative
+        psImage *image = readout->image;// The proper image
+        *trimsec = sectionForImage(&position, image, readdir);
+    }
+
+    // A final run through to do the RHS biases
+    psListIteratorSet(targetsIter, cellNum);
+    psListIteratorSet(sourcesIter, cellNum);
+    while ((target = psListGetAndIncrement(targetsIter)) && (source = psListGetAndIncrement(sourcesIter))) {
+        doBiasSections(&position, target, source);
+    }
+
+    // Clean up
+    psFree(targetsIter);
+    psFree(sourcesIter);
+
+    return (position > 0);
+}
+
+
+// Generate an HDU with the pixels
+static bool generateHDU(pmCell *target,  // The target cell
+                        pmCell *source, // The source cell
+                        int xBin, int yBin // Binning in x and y
+                       )
+{
+    // Get the HDU and a list of cells below it
+    pmHDU *hdu = pmHDUFromCell(target); // The HDU in the target cell
+    psList *targetCells = psListAlloc(NULL); // List of target cells below the target HDU
+    psList *sourceCells = psListAlloc(NULL); // List of source cells below the target HDU
+    if (! cellList(targetCells, sourceCells, target, source)) {
+        psError(PS_ERR_IO, true, "Unable to find cells to generate HDU!\n");
+        return false;
+    }
+
+    // Check the number of readouts
+    int numReadouts = -1;               // Number of readouts
+    {
+        psListIterator *iter = psListIteratorAlloc(sourceCells, 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: %d vs %d\n", readouts->n,
+                        numReadouts);
+                return false;
+            }
+
+        }
+        psFree(iter);
+    }
+
+    // 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, targetCells) && !(generateTrimBias(targetCells, sourceCells) &&
+            sizeHDU(&xSize, &ySize, targetCells))) {
+        psError(PS_ERR_IO, true, "Unable to determine size of HDU!\n");
+        return false;
+    }
+    psFree(targetCells);
+    psFree(sourceCells);
+
+    xSize = (int)ceilf((float)xSize/(float)xBin);
+    ySize = (int)ceilf((float)ySize/(float)yBin);
+
+    hdu->images = psArrayAlloc(numReadouts);
+    hdu->images->n = numReadouts;
+    for (int i = 0; i < numReadouts; i++) {
+        psImage *image = psImageAlloc(xSize, ySize, PS_TYPE_F32);
+        psImageInit(image, 0.0);
+        hdu->images->data[i] = image;
+    }
+
+    return true;
+}
+
+// Copy pixels from a target image to a source image, with flips
+static bool copyPixels(psImage *target, // Target image (HDU pixels)
+                       psImage *source, // Source image (from source cell)
+                       psRegion region, // Region for pasting
+                       bool xFlip,      // Flip in x?
+                       bool yFlip       // Flip in y?
+                      )
+{
+    psImage *overlay = psMemIncrRefCounter(source);
+    if (xFlip) {
+        psImage *temp = psImageFlipX(overlay);
+        psFree(overlay);
+        overlay = temp;
+    }
+    if (yFlip) {
+        psImage *temp = psImageFlipY(overlay);
+        psFree(overlay);
+        overlay = temp;
+    }
+    int numPix = psImageOverlaySection(target, overlay, region.x0, region.y0, "=");
+    psFree(overlay);
+    return (numPix > 0);
+}
+
+// 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
+                     )
+{
+    // 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);
+}
+
+
+static pmHDU *findPHU(const pmCell *cell// The cell for which to find the PHU
+                     )
+{
+    if (cell->hdu && cell->hdu->phu) {
+        return cell->hdu;
+    }
+    pmChip *chip = cell->parent;        // The parent chip
+    if (chip->hdu && chip->hdu->phu) {
+        return chip->hdu;
+    }
+    pmFPA *fpa = chip->parent;  // The parent FPA
+    if (fpa->hdu && fpa->hdu->phu) {
+        return fpa->hdu;
+    }
+
+    psError(PS_ERR_IO, true, "Unable to find PHU for target cell.\n");
+    return NULL;
+}
+
+
+static bool copyPHU(pmCell *targetCell, // The target cell
+                    pmCell *sourceCell  // The source cell
+                   )
+{
+    // Find the respective PHUs
+    pmHDU *targetPHU = findPHU(targetCell); // The target PHU
+    if (targetPHU->header) {
+        return false;                   // No work required
+    }
+    // Copy the header over
+    pmHDU *sourcePHU = findPHU(sourceCell); // The source PHU
+    targetPHU->header = psMetadataCopy(NULL, sourcePHU->header);
+    return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 int 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);
+
+    psArray *sourceReadouts = source->readouts; // The source readouts
+    int numReadouts = sourceReadouts->n; // Number of readouts copied
+
+    // Copy any headers
+    if (target->hdu && !target->hdu->phu) {
+        pmHDU *sourceHDU = pmHDUFromCell(source);
+        target->hdu->header = psMetadataCopy(target->hdu->header, sourceHDU->header);
+    }
+
+    pmHDU *hdu = pmHDUFromCell(target); // The target HDU; we need to fix this up
+    if (!hdu->images) {
+        generateHDU(target, source, xBin, yBin);
+    }
+    if (!hdu->header) {
+        hdu->header = psMetadataAlloc();
+    }
+    // Copy the PHU over as well, if required
+    copyPHU(target, source);
+
+    // Need to check/change CELL.XPARITY and CELL.YPARITY
+    bool xFlip = (psMetadataLookupS32(NULL, target->concepts, "CELL.XPARITY") !=
+                  psMetadataLookupS32(NULL, source->concepts, "CELL.XPARITY")); // Switch parity in x?
+    bool yFlip = (psMetadataLookupS32(NULL, target->concepts, "CELL.YPARITY") !=
+                  psMetadataLookupS32(NULL, source->concepts, "CELL.YPARITY")); // Switch parity in y?
+    psTrace(__func__, 3, "xFlip: %d; yFlip: %d\n", xFlip, yFlip);
+
+    bool mdok = true;                   // Status of MD lookup
+    psRegion *trimsec = psMetadataLookupPtr(&mdok, target->concepts, "CELL.TRIMSEC"); // The trim section
+    if (!mdok || !trimsec || psRegionIsBad(*trimsec)) {
+        psError(PS_ERR_IO, true, "CELL.TRIMSEC isn't set!\n");
+        return 0;
+    }
+    psList *biassecs = psMetadataLookupPtr(&mdok, target->concepts, "CELL.BIASSEC"); // The bias sections
+    if (!mdok || !biassecs) {
+        psError(PS_ERR_IO, true, "CELL.BIASSEC isn't set!\n");
+        return 0;
+    }
+
+    for (int i = 0; i < numReadouts; i++) {
+        pmReadout *sourceReadout = sourceReadouts->data[i]; // The source readout
+        psImage *sourceImage = sourceReadout->image; // The source image
+        pmReadout *targetReadout = pmReadoutAlloc(target); // The target readout; this adds it to the cell
+        if (sourceImage->numCols != trimsec->x1 - trimsec->x0 ||
+                sourceImage->numRows != trimsec->y1 - trimsec->y0) {
+            psString trimsecString = psRegionToString(*trimsec); // String with the trim section
+            psLogMsg(__func__, PS_LOG_WARN, "Source image size (%dx%d) for readout %d doesn't match "
+                     "CELL.TRIMSEC for target (%s) -- ignored.\n", sourceImage->numCols, sourceImage->numRows,
+                     i, trimsecString);
+            psFree(trimsecString);
+        } else {
+            binRegion(trimsec, xBin, yBin);
+            if (pixels) {
+                copyPixels(hdu->images->data[i], sourceImage, *trimsec, xFlip, yFlip);
+            }
+            targetReadout->image = psImageSubset(hdu->images->data[i], *trimsec);
+        }
+
+        psListIterator *biassecsIter = psListIteratorAlloc(biassecs, PS_LIST_HEAD, false); // Iterator
+        psRegion *biassec = NULL;       // Bias section from iteration
+        psListIterator *biasIter = psListIteratorAlloc(sourceReadout->bias, PS_LIST_HEAD, false); // Iterator
+        psImage *bias = NULL;           // Bias image from iteration
+        while ((biassec = psListGetAndIncrement(biassecsIter)) && (bias = psListGetAndIncrement(biasIter))) {
+            if (psRegionIsBad(*biassec)) {
+                psString biassecString = psRegionToString(*biassec); // String for bias section
+                psLogMsg(__func__, PS_LOG_WARN, "Bias section (%s) isn't set --- ignored.\n", biassecString);
+                psFree(biassecString);
+                continue;
+            }
+            if (bias->numCols != biassec->x1 - biassec->x0 ||
+                    bias->numRows != biassec->y1 - biassec->y0) {
+                psString biassecString = psRegionToString(*biassec); // String with the bias section
+                psLogMsg(__func__, PS_LOG_WARN, "Source image size (%dx%d) for readout %d doesn't match "
+                         "CELL.BIASSEC for target (%s) -- ignored.\n", bias->numCols, bias->numRows, i,
+                         biassecString);
+                psFree(biassecString);
+            } else {
+                binRegion(biassec, xBin, yBin);
+                if (pixels) {
+                    copyPixels(hdu->images->data[i], bias, *biassec, xFlip, yFlip);
+                }
+                psImage *newBias = psImageSubset(hdu->images->data[i], *biassec);
+                psListAdd(targetReadout->bias, PS_LIST_TAIL, newBias);
+                psFree(newBias);        // Drop reference
+            }
+        }
+        psFree(targetReadout);          // Drop reference
+        psFree(biassecsIter);
+        psFree(biasIter);
+    }
+
+    // Copy the remaining "concepts" over
+    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) {
+            psMetadataAddItem(target->concepts, conceptItem, PS_LIST_TAIL, PS_META_REPLACE);
+        }
+    }
+    psFree(conceptsIter);
+
+    // 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(__func__, 3, "CELL.X0: Before: %d After: %d\n", xZero,
+                xZero - (readout->image->numCols - 1) * xParity * xBin);
+        psTrace(__func__, 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(__func__, 3, "CELL.Y0: Before: %d After: %d\n", yZero,
+                yZero - (readout->image->numRows - 1) * yParity * yBin);
+        psTrace(__func__, 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;
+
+    return numReadouts;
+}
+
+static int 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);
+
+    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 (%d) differs from the number of target cells (%d)\n",
+                sourceCells->n, targetCells->n);
+        return false;
+    }
+
+    // Copy any headers
+    if (target->hdu && !target->hdu->phu) {
+        pmHDU *sourceHDU = pmHDUFromChip(source);
+        target->hdu->header = psMetadataCopy(target->hdu->header, sourceHDU->header);
+    }
+
+    int numCells = 0;                   // Number of cells copied
+    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
+            int numReadouts = cellCopy(targetCell, sourceCell, pixels, xBin, yBin); // Number of readouts
+            // copied
+            psTrace(__func__, 5, "Copied %d readouts for cell %s\n", numReadouts, cellName);
+            numCells++;
+        }
+    }
+
+    // Update the concepts
+    psMetadataCopy(target->concepts, source->concepts);
+
+    return numCells;
+
+}
+
+static int 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);
+
+    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 (%d) differs from the number of target chips (%d)\n",
+                sourceChips->n, targetChips->n);
+        return false;
+    }
+
+    // Copy any headers
+    if (target->hdu && !target->hdu->phu) {
+        pmHDU *sourceHDU = pmHDUFromFPA(source);
+        target->hdu->header = psMetadataCopy(target->hdu->header, sourceHDU->header);
+    }
+
+    int numChips = 0;                   // Number of chips copied
+    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
+            int numCells = chipCopy(targetChip, sourceChip, pixels, xBin, yBin); // Number of cells copied
+            psTrace(__func__, 5, "Copied %d cells for chip %s\n", numCells, chipName);
+            numChips++;
+        }
+    }
+
+    // Update the concepts
+    psMetadataCopy(target->concepts, source->concepts);
+
+    return numChips;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+int pmFPACopy(pmFPA *target,            // The target FPA
+              pmFPA *source             // The source FPA, to be copied
+             )
+{
+    return fpaCopy(target, source, true, 1, 1);
+}
+
+int pmChipCopy(pmChip *target,          // The target chip
+               pmChip *source           // The source chip, to be copied
+              )
+{
+    return chipCopy(target, source, true, 1, 1);
+}
+
+int pmCellCopy(pmCell *target,          // The target cell
+               pmCell *source           // The source cell, to be copied
+              )
+{
+    return cellCopy(target, source, true, 1, 1);
+}
+
+
+int pmFPACopyStructure(pmFPA *target,   // The target FPA
+                       pmFPA *source,   // The source FPA, to be copied
+                       int xBin, int yBin // Binning factors in x and y
+                      )
+{
+    return fpaCopy(target, source, false, xBin, yBin);
+}
+
+int pmChipCopyStructure(pmChip *target, // The target chip
+                        pmChip *source, // The source chip, to be copied
+                        int xBin, int yBin // Binning factors in x and y
+                       )
+{
+    return chipCopy(target, source, false, xBin, yBin);
+}
+
+int pmCellCopyStructure(pmCell *target, // The target cell
+                        pmCell *source, // The source cell, to be copied
+                        int xBin, int yBin // Binning factors in x and y
+                       )
+{
+    return cellCopy(target, source, false, xBin, yBin);
+}
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPACopy.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPACopy.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPACopy.h	(revision 6961)
@@ -0,0 +1,30 @@
+#ifndef PM_FPA_COPY_H
+#define PM_FPA_COPY_H
+
+// Copy the FPA components, including the pixels
+int pmFPACopy(pmFPA *target,            // The target FPA
+              pmFPA *source             // The source FPA, to be copied
+             );
+int pmChipCopy(pmChip *target,          // The target chip
+               pmChip *source           // The source chip, to be copied
+              );
+int 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
+int pmFPACopyStructure(pmFPA *target,   // The target FPA
+                       pmFPA *source,   // The source FPA, to be copied
+                       int xBin, int yBin     // Binning factors in x and y
+                      );
+int pmChipCopyStructure(pmChip *target, // The target chip
+                        pmChip *source, // The source chip, to be copied
+                        int xBin, int yBin   // Binning factors in x and y
+                       );
+int 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/rel11_ifa_pre0/psModules/src/astrom/pmFPAHeader.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAHeader.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAHeader.c	(revision 6961)
@@ -0,0 +1,99 @@
+#include <stdio.h>
+#include <assert.h>
+#include "pslib.h"
+#include "pmFPA.h"
+#include "pmHDU.h"
+#include "pmConcepts.h"
+#include "pmFPAHeader.h"
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// File-static (private) functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Read concepts for all cells in a chip
+static bool chipConcepts(pmChip *chip   // The chip for which to read the concepts
+                        )
+{
+    bool status = true;                 // Status of concept reading
+    status |= pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER, false, NULL);
+    psArray *cells = chip->cells;       // The cells
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // The cell of interest
+        if (!cell) {
+            continue;
+        }
+        status |= pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, false, NULL);
+    }
+
+    return status;
+}
+
+// Read concepts for all chips in an FPA
+static bool fpaConcepts(pmFPA *fpa   // The FPA for which to read the concepts
+                       )
+{
+    bool status = true;                 // Status of concept reading
+    status |= pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER, NULL);
+    psArray *chips = fpa->chips;        // The chips
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];  // The chip of interest
+        if (!chip) {
+            continue;
+        }
+        status |= chipConcepts(chip);
+    }
+
+    return status;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+bool pmCellReadHeader(pmCell *cell,     // Cell for which to read header
+                      psFits *fits      // FITS file handle
+                     )
+{
+    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
+                     )
+{
+    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;
+    }
+
+    return chipConcepts(chip);
+}
+
+
+bool pmFPAReadHeader(pmFPA *fpa,        // FPA for which to read header
+                     psFits *fits       // FITS file handle
+                    )
+{
+    if (!fpa->hdu) {
+        return false;
+    }
+    if (!pmHDUReadHeader(fpa->hdu, fits)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell.\n");
+        return false;
+    }
+
+    return fpaConcepts(fpa);
+}
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAHeader.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAHeader.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAHeader.h	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/astrom/pmFPAMaskWeight.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAMaskWeight.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAMaskWeight.c	(revision 6961)
@@ -0,0 +1,101 @@
+#include <stdio.h>
+#include "pslib.h"
+#include "pmFPA.h"
+#include "pmFPAMaskWeight.h"
+
+bool pmReadoutSetMask(pmReadout *readout // Readout for which to set mask
+                     )
+{
+    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 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(NULL, 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;
+    }
+
+    psImage *image = readout->image;    // The image pixels
+    psImage *mask = readout->mask;      // The mask pixels
+    if (!mask) {
+        mask = psImageAlloc(image->numCols, image->numRows, PS_TYPE_U8);
+        readout->mask = mask;
+        psImageInit(mask, 0);
+    }
+
+    // Set up the mask
+    for (int i = 0; i < image->numRows; i++) {
+        for (int j = 0; j < image->numCols; j++) {
+            if (image->data.F32[i][j] > saturation) {
+                mask->data.U8[i][j] |= PM_MASK_SAT;
+            }
+            if (image->data.F32[i][j] < bad) {
+                mask->data.U8[i][j] |= PM_MASK_BAD;
+            }
+        }
+    }
+
+    return true;
+}
+
+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;
+    }
+
+    psImage *image = readout->image;    // The image pixels
+    psImage *weight = readout->weight;  // The weight pixels
+    if (!weight) {
+        weight = psImageAlloc(image->numCols, image->numRows, PS_TYPE_F32);
+        readout->weight = weight;
+        psImageInit(weight, 0.0);
+    }
+
+    // Set weight image to the variance in ADU = f/g + rn^2
+    psBinaryOp(readout->weight, image, "/", psScalarAlloc(gain, PS_TYPE_F32));
+    psBinaryOp(readout->weight, readout->weight, "+",
+               psScalarAlloc(readnoise*readnoise/gain/gain, PS_TYPE_F32));
+
+    return true;
+}
+
+
+
+bool pmCellSetMaskWeight(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 |= pmReadoutSetMask(readout);
+        success |= pmReadoutSetWeight(readout);
+    }
+
+    return success;
+}
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAMaskWeight.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAMaskWeight.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAMaskWeight.h	(revision 6961)
@@ -0,0 +1,23 @@
+#ifndef PM_READOUT_MASK_WEIGHT_H
+#define PM_READOUT_MASK_WEIGHT_H
+
+#include "pmFPA.h"
+
+/** Mask values */
+typedef enum {
+    PM_MASK_TRAP    = 0x0001,   ///< The pixel is a charge trap.
+    PM_MASK_BADCOL  = 0x0002,   ///< The pixel is a bad column.
+    PM_MASK_SAT     = 0x0004,   ///< The pixel is saturated.
+    PM_MASK_BAD     = 0x0008,   ///< The pixel is low
+    PM_MASK_FLAT    = 0x0010    ///< The pixel is non-positive in the flat-field.
+} pmMaskValue;
+
+bool pmReadoutSetMask(pmReadout *readout // Readout for which to set mask
+                     );
+bool pmReadoutSetWeight(pmReadout *readout // Readout for which to set weight
+                       );
+bool pmCellSetMaskWeight(pmCell *cell // Cell for which to set weights
+                        );
+
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPARead.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPARead.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPARead.c	(revision 6961)
@@ -0,0 +1,295 @@
+#include <stdio.h>
+#include <strings.h>
+#include <assert.h>
+#include "pslib.h"
+
+#include "pmFPA.h"
+#include "pmFPARead.h"
+#include "pmHDU.h"
+#include "pmHDUUtils.h"
+#include "pmConcepts.h"
+#include "psRegionIsBad.h"
+#include "pmFPAHeader.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 *image, // Image that will be carved
+                         const psRegion *trimsec, // Trim section
+                         const psList *biassecs // Bias sections
+                        )
+{
+    // The image corresponding to the trim region
+    if (psRegionIsBad(*trimsec)) {
+        psString regionString = psRegionToString(*trimsec);
+        psError(PS_ERR_IO, true, "Invalid trim section: %s\n", regionString);
+        psFree(regionString);
+        psFree(readout);
+        return NULL;
+    }
+    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!
+    }
+    readout->image = 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 (psRegionIsBad(*biassec)) {
+            psString regionString = psRegionToString(*biassec);
+            psError(PS_ERR_IO, true, "Invalid bias section: %s\n", regionString);
+            psFree(regionString);
+            psFree(readout);
+            return NULL;
+        }
+        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 readout;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// Public functions
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+// Read the next readout; return true if we read pixels in.
+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
+                      )
+{
+    // Get the HDU and read the header
+    pmCell *cell = readout->parent;     // The parent cell
+    if (!pmCellReadHeader(cell, fits)) {
+        psError(PS_ERR_IO, false, "Unable to read header for cell!\n");
+        return false;
+    }
+
+    pmHDU *hdu = pmHDUFromCell(cell);   // The HDU
+    if (!hdu) {
+        return false;
+    }
+
+    // Make sure we have the information we need
+    pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, false, NULL);
+
+    // 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 || psRegionIsBad(*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 != 1)) {
+        psError(PS_ERR_IO, true, "CELL.READDIR is not set to -1 or +1.\n");
+        return false;
+    }
+
+    // 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;
+    }
+
+    // Read the FITS image
+    int offset = readout->row0;         // The row number to read
+    if (readout->image) {
+        if (readdir > 0) {
+            // Reading rows
+            offset += readout->image->numRows;
+        } else {
+            // Reading columns
+            offset += readout->image->numCols;
+        }
+    }
+    if ((readdir > 0 && offset >= naxis2) || (readdir < 0 && offset > naxis1)) {
+        // We've read everything there is
+        return false;
+    }
+    int upper = offset + numScans;      // The upper limit for the pixel read
+    if (readdir > 0 && upper > naxis2) {
+        upper = naxis2;
+    } else if (readdir < 0 && upper > naxis1) {
+        upper = naxis1;
+    }
+    psRegion region = {0, 0, 0, 0};     // Region to be read
+    if (readdir > 0) {
+        region = psRegionSet(0, naxis1, offset, upper); // Read rows
+    } else {
+        region = psRegionSet(offset, upper, 0, naxis2); // Read columns
+    }
+    psImage *image = psFitsReadImage(fits, region, z); // The image
+
+    // Stick the image into the HDU and the readout
+    if (!hdu->images) {
+        hdu->images = psArrayAlloc(naxis3);
+        hdu->images->n = naxis3;
+    }
+    if (hdu->images->nalloc != naxis3) {
+        hdu->images = psArrayRealloc(hdu->images, naxis3);
+        hdu->images->n = naxis3;
+    }
+    if (hdu->images->data[z]) {
+        psFree(hdu->images->data[z]);
+    }
+    hdu->images->data[z] = image;
+    readout->row0 = region.y0;
+    readout->col0 = region.x0;
+    readoutCarve(readout, image, trimsec, biassecs);
+
+    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
+               )
+{
+    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_IO, false, "Unable to read HDU for cell.\n");
+        return false;
+    }
+
+    pmConceptsReadCell(cell, PM_CONCEPT_SOURCE_HEADER, false, NULL);
+
+    // 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");
+
+    // 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 = pmReadoutAlloc(cell);
+        readoutCarve(readout, image, trimsec, biassecs);
+        readout->mask = NULL;
+        readout->weight = 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
+               )
+{
+    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) {
+        pmConceptsReadChip(chip, PM_CONCEPT_SOURCE_HEADER, false, NULL);
+        // 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
+              )
+{
+    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) {
+        pmConceptsReadFPA(fpa, PM_CONCEPT_SOURCE_HEADER, NULL);
+    } else {
+        psLogMsg(__func__, PS_LOG_WARN, "Unable to read any chips in FPA.\n");
+    }
+
+    return success;
+}
+
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPARead.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPARead.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPARead.h	(revision 6961)
@@ -0,0 +1,66 @@
+#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 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/rel11_ifa_pre0/psModules/src/astrom/pmFPAUtils.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAUtils.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAUtils.c	(revision 6961)
@@ -0,0 +1,41 @@
+#include <stdio.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
+                 )
+{
+    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
+                  )
+{
+    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/rel11_ifa_pre0/psModules/src/astrom/pmFPAUtils.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAUtils.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAUtils.h	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/astrom/pmFPAWrite.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAWrite.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAWrite.c	(revision 6961)
@@ -0,0 +1,147 @@
+#include <stdio.h>
+#include <strings.h>
+#include "pslib.h"
+
+#include "pmFPA.h"
+#include "pmHDU.h"
+#include "pmHDUUtils.h"
+#include "pmConcepts.h"
+
+
+bool pmReadoutWriteNext(pmReadout *readout, // Readout to write
+                        psFits *fits,   // FITS file to which to write
+                        int z           // Image plane to write
+                       )
+{
+    pmHDU *hdu = pmHDUFromReadout(readout); // The HDU to which to write
+    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;
+    }
+
+    psImage *image = hdu->images->data[z]; // 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 (!psFitsMoveExtName(fits, hdu->extname)) {
+        psError(PS_ERR_IO, false, "Unable to move to extension %s\n", hdu->extname);
+        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 pixels            // Write the pixels?
+                )
+{
+    pmConceptsWriteCell(cell, PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CAMERA | PM_CONCEPT_SOURCE_DEFAULTS,
+                        false, NULL);
+
+    pmHDU *hdu = cell->hdu;             // The HDU
+    if (hdu && ((!pixels && hdu->phu) || pixels) && !pmHDUWrite(hdu, fits)) {
+        psError(PS_ERR_IO, false, "Unable to write HDU for Chip.\n");
+        return false;
+    }
+
+    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 pixels            // Write the pixels?
+                )
+{
+    pmConceptsWriteChip(chip, PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CAMERA | PM_CONCEPT_SOURCE_DEFAULTS,
+                        false, NULL);
+
+    pmHDU *hdu = chip->hdu;             // The HDU
+    if (hdu && ((!pixels && hdu->phu) || pixels) && !pmHDUWrite(hdu, fits)) {
+        psError(PS_ERR_IO, false, "Unable to write HDU for Chip.\n");
+        return false;
+    }
+
+    bool success = true;                // Success of writing
+    if (pixels) {
+        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 |= pmCellWrite(cell, fits, db, true);
+        }
+    }
+
+    return success;
+}
+
+
+
+
+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?
+               )
+{
+    pmConceptsWriteFPA(fpa, PM_CONCEPT_SOURCE_HEADER | PM_CONCEPT_SOURCE_CAMERA | PM_CONCEPT_SOURCE_DEFAULTS,
+                       NULL);
+
+    pmHDU *hdu = fpa->hdu;              // The HDU
+    if (hdu && ((!pixels && hdu->phu) || pixels) && !pmHDUWrite(hdu, fits)) {
+        psError(PS_ERR_IO, false, "Unable to write HDU for FPA.\n");
+        return false;
+    }
+
+    bool success = true;                // Success of writing
+    if (pixels) {
+        psArray *chips = fpa->chips;        // Array of chips
+        for (int i = 0; i < chips->n; i++) {
+            pmChip *chip = chips->data[i];  // The chip of interest
+            success |= pmChipWrite(chip, fits, db, true);
+        }
+    }
+
+    return success;
+}
+
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAWrite.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAWrite.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAWrite.h	(revision 6961)
@@ -0,0 +1,28 @@
+#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?
+                );
+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?
+                );
+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?
+               );
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPA_JPEG.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPA_JPEG.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPA_JPEG.c	(revision 6961)
@@ -0,0 +1,163 @@
+/** @file  pmFPA_JPEG.c
+ *
+ * This file contains functions to write JPEG images.
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:01:04 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+/*****************************************************************************/
+/* INCLUDE FILES                                                             */
+/*****************************************************************************/
+
+#include <pslib.h>
+# include "psImageJpeg.h"
+# include "psImageUnbin.h"
+# include "psAdditionals.h"
+
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmFPAfile.h"
+#include "pmFPAview.h"
+#include "pmFPA_JPEG.h"
+
+
+bool pmFPAviewWriteJPEG (const pmFPAview *view, pmFPAfile *file)
+{
+
+    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)
+{
+
+    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)
+{
+
+    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)
+{
+
+    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)
+{
+    char *name, *mode, *word, *mapname;
+    psArray *range;
+    psImageJpegColormap *map;
+
+    switch (file->type) {
+    case PM_FPA_FILE_JPEG:
+
+        name = pmFPAfileNameFromRule (file->filerule, file, view);
+
+        mapname = pmFPAfileNameFromRule (file->filextra, file, view);
+        map = psImageJpegColormapSet (NULL, mapname);
+        if (!map) {
+            map = psImageJpegColormapSet (NULL, "-greyscale");
+        }
+
+        mode = pmFPAfileNameFromRule (file->extrule, file, view);
+        word = pmFPAfileNameFromRule (file->extxtra, file, view);
+        range = psStringSplitArray (word, ":");
+
+        // XXX we need to decide where the scale will come from in the long term
+        psImageClippedStatsInit (10000);
+
+        psStats *stats = psImageClippedStats (readout->image, NULL, 0, 0.25, 0.75);
+        float delta = stats->robustUQ - stats->robustLQ;
+        float min = stats->robustMedian - 3*delta;
+        float max = stats->robustMedian - 6*delta;
+        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;
+        }
+        if (!strcasecmp (mode, "FRACTION")) {
+            float fmin = atof(range->data[0]);
+            float fmax = atof(range->data[1]);
+            min = fmin*stats->robustMedian;
+            max = fmax*stats->robustMedian;
+        }
+        psImageJpeg (map, readout->image, name, min, max);
+
+        psFree (name);
+        psFree (mode);
+        psFree (word);
+        psFree (mapname);
+        psFree (map);
+        psFree (stats);
+        psImageClippedStatsCleanup ();
+
+        return true;
+
+    default:
+        fprintf (stderr, "warning: type mismatch\n");
+        break;
+    }
+    return false;
+}
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPA_JPEG.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPA_JPEG.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPA_JPEG.h	(revision 6961)
@@ -0,0 +1,27 @@
+/** @file  pmFPAview.h
+*
+*  @brief Tools to manipulate the FPA structure elements.
+*
+*  @ingroup AstroImage
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-04-17 18:01:04 $
+*
+*  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/rel11_ifa_pre0/psModules/src/astrom/pmFPAfile.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAfile.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAfile.c	(revision 6961)
@@ -0,0 +1,1144 @@
+#include <stdio.h>
+#include "pslib.h"
+#include "psAdditionals.h"
+#include "pmConfig.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmMaskBadPixels.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 == NULL)
+        return;
+
+    psFree (file->fpa);
+    psFree (file->readout);
+    psFree (file->names);
+
+    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);
+
+    // these are just views ??
+    // psFree (file->phu);
+    // psFree (file->header);
+
+    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->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;
+
+    return (file);
+}
+
+pmFPAfile *pmFPAfileDefine (psMetadata *files, psMetadata *camera, pmFPA *fpa, char *name)
+{
+
+    bool status;
+    char *depth;
+    char *type;
+
+    // select the FILERULES from the camera config
+    psMetadata *filerules = psMetadataLookupPtr (&status, camera, "FILERULES");
+    if (filerules == NULL) {
+        psErrorStackPrint(stderr, "Can't find FILERULES in the CAMERA configuration!\n");
+        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 == NULL)
+        realname = name;
+
+    psMetadata *data = psMetadataLookupPtr (&status, filerules, realname);
+    if (data == NULL) {
+        psErrorStackPrint(stderr, "Can't find file concept %s!\n", 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->fileDepth = PM_FPA_DEPTH_NONE;
+    depth = psMetadataLookupStr (&status, data, "FILE.DEPTH");
+    if (depth != NULL) {
+        if (!strcasecmp (depth, "FPA"))     {
+            file->fileDepth = PM_FPA_DEPTH_FPA;
+        }
+        if (!strcasecmp (depth, "CHIP"))    {
+            file->fileDepth = PM_FPA_DEPTH_CHIP;
+        }
+        if (!strcasecmp (depth, "CELL"))    {
+            file->fileDepth = PM_FPA_DEPTH_CELL;
+        }
+        if (!strcasecmp (depth, "READOUT")) {
+            file->fileDepth = PM_FPA_DEPTH_READOUT;
+        }
+    }
+    if (file->fileDepth == PM_FPA_DEPTH_NONE) {
+        psLogMsg (__func__, 3, "warning: FILE.DEPTH is not set for %s\n", name);
+    }
+
+    file->dataDepth = PM_FPA_DEPTH_NONE;
+    depth = psMetadataLookupStr (&status, data, "DATA.DEPTH");
+    if (depth != NULL) {
+        if (!strcasecmp (depth, "FPA"))     {
+            file->dataDepth = PM_FPA_DEPTH_FPA;
+        }
+        if (!strcasecmp (depth, "CHIP"))    {
+            file->dataDepth = PM_FPA_DEPTH_CHIP;
+        }
+        if (!strcasecmp (depth, "CELL"))    {
+            file->dataDepth = PM_FPA_DEPTH_CELL;
+        }
+        if (!strcasecmp (depth, "READOUT")) {
+            file->dataDepth = PM_FPA_DEPTH_READOUT;
+        }
+    }
+    if (file->dataDepth == PM_FPA_DEPTH_NONE) {
+        psLogMsg (__func__, 3, "warning: DATA.DEPTH is not set for %s\n", name);
+    }
+
+    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;
+        }
+        if (!strcasecmp (type, "OBJ"))     {
+            file->type = PM_FPA_FILE_OBJ;
+        }
+        if (!strcasecmp (type, "CMP"))     {
+            file->type = PM_FPA_FILE_CMP;
+        }
+        if (!strcasecmp (type, "CMF"))     {
+            file->type = PM_FPA_FILE_CMF;
+        }
+        if (!strcasecmp (type, "RAW"))     {
+            file->type = PM_FPA_FILE_RAW;
+        }
+        if (!strcasecmp (type, "IMAGE"))     {
+            file->type = PM_FPA_FILE_IMAGE;
+        }
+        if (!strcasecmp (type, "PSF"))     {
+            file->type = PM_FPA_FILE_PSF;
+        }
+        if (!strcasecmp (type, "JPEG"))     {
+            file->type = PM_FPA_FILE_JPEG;
+        }
+    }
+    if (file->type == PM_FPA_FILE_NONE) {
+        psLogMsg (__func__, 3, "warning: FILE.TYPE is not defined for %s\n", name);
+    }
+
+    file->mode = PM_FPA_MODE_NONE;
+    type = psMetadataLookupStr (&status, data, "FILE.MODE");
+    if (type != NULL) {
+        if (!strcasecmp (type, "READ"))     {
+            file->mode = PM_FPA_MODE_READ;
+        }
+        if (!strcasecmp (type, "WRITE"))     {
+            file->mode = PM_FPA_MODE_WRITE;
+        }
+    }
+    if (file->mode == PM_FPA_MODE_NONE) {
+        psLogMsg (__func__, 3, "warning: FILE.MODE is not defined for %s\n", name);
+    }
+
+    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
+    char *formatName = psMetadataLookupStr (&status, data, "FILE.FORMAT");
+    if (formatName && strcasecmp (formatName, "NONE")) {
+        psMetadata *formats = psMetadataLookupMD(&status, camera, "FORMATS"); // List of formats
+        char *formatFile = psMetadataLookupStr (&status, formats, formatName);
+        readConfig (&file->format, formatFile, formatName);
+    }
+
+    psMetadataAddPtr (files, PS_LIST_TAIL, name, PS_DATA_UNKNOWN, "", file);
+
+    // we free this copy of file, but 'files' still has a copy
+    psFree (file);
+
+    // the returned value is a view into the version on 'files'
+    return (file);
+}
+
+/*
+pmFPAfile *pmFPAfileConstruct (psMetadata *files, psMetadata *format, psMetadata *camera, char *name)
+{
+    pmFPA *fpa = pmFPAConstruct (camera);
+    pmFPAfile *file = pmFPAfileDefine (files, format, fpa, name);
+    psFree (fpa);
+    return file;
+}
+*/
+
+// open file (if not already opened)
+bool pmFPAfileOpen (pmFPAfile *file, const pmFPAview *view)
+{
+
+    bool status;
+    char *extra;
+    char *mode = NULL;
+    char *readMode = "r";
+    char *writeMode = "w";
+
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        return false;
+    }
+
+    if (file->state == PM_FPA_STATE_OPEN) {
+        return true;
+    }
+
+    if (file->mode == PM_FPA_MODE_NONE) {
+        return false;
+    }
+    if (file->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
+    file->filename = pmFPAfileNameFromRule (file->filerule, file, view);
+    if (file->filename == NULL)
+        return false;
+
+    // indirect filenames
+    if (!strcasecmp (file->filename, "@FILES")) {
+        psFree (file->filename);
+        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);
+    }
+    if (!strcasecmp (file->filename, "@DETDB")) {
+        psFree (file->filename);
+        extra = pmFPAfileNameFromRule (file->filextra, file, view);
+        // file->filename = pmDetrendSelect (extra);
+        psFree (extra);
+        if (file->filename == NULL)
+            return false;
+        psMemIncrRefCounter (file->filename);
+    }
+
+    switch (file->type) {
+        // open the FITS types:
+    case PM_FPA_FILE_IMAGE:
+    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)
+            psAbort (__func__, "error opening file %s\n", file->filename);
+        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, "not opening %s (type: %d)\n", file->filename, file->type);
+        break;
+
+    default:
+        fprintf (stderr, "warning: type mismatch for %s : %d\n", file->filename, file->type);
+        return false;
+    }
+    return true;
+}
+
+bool pmFPAfileRead (pmFPAfile *file, const pmFPAview *view)
+{
+    if (file->state & PM_FPA_STATE_INACTIVE)
+        return false;
+
+    if (file->mode != PM_FPA_MODE_READ)
+        return false;
+
+    // get the current depth
+    pmFPAdepth depth = pmFPAviewDepth (view);
+
+    // do we need to open this file?
+    if (depth == file->fileDepth) {
+        pmFPAfileOpen (file, view);
+    }
+
+    // do we need to read this file?
+    if (depth != file->dataDepth)
+        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 {
+            psTrace ("pmFPAfile", 5, "skipping %s (type: %d)\n", file->filename, file->type);
+        }
+        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:
+        fprintf (stderr, "warning: type mismatch\n");
+        return false;
+    }
+    return true;
+}
+
+bool pmFPAfileWrite (pmFPAfile *file, const pmFPAview *view)
+{
+
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        return false;
+    }
+
+    if (file->mode != PM_FPA_MODE_WRITE) {
+        return false;
+    }
+
+    // get the current depth
+    pmFPAdepth depth = pmFPAviewDepth (view);
+
+    // do we need to write this file?
+    if (depth != file->dataDepth)
+        return false;
+
+    // do we need to open this file?
+    if (depth >= file->fileDepth) {
+        pmFPAfileOpen (file, view);
+    }
+
+    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:
+        pmFPAviewWriteObjects (view, file);
+        psTrace ("pmFPAfile", 5, "wrote object %s (fpa: %p)\n", file->filename, file->fpa);
+        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)
+{
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        return false;
+    }
+    if (file->mode != PM_FPA_MODE_WRITE)
+        return false;
+
+    // get the current depth
+    pmFPAdepth depth = pmFPAviewDepth (view);
+
+    // do we need to write this file?
+    if (depth != file->dataDepth)
+        return false;
+
+    // XXX is this a sufficient check to avoid creating elements?
+    if (file->src == NULL)
+        return false;
+
+    switch (file->type) {
+    case PM_FPA_FILE_IMAGE:
+        /* create a PHU for thie file, if it does not exist */
+        pmFPAfileCopyStructureView (file->fpa, file->src, file->format, file->xBin, file->yBin, view);
+        psTrace ("pmFPAfile", 5, "created fpa data elements for %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:
+    case PM_FPA_FILE_PSF:
+    case PM_FPA_FILE_JPEG:
+        break;
+
+    default:
+        fprintf (stderr, "warning: type mismatch\n");
+        return false;
+    }
+    return true;
+}
+
+bool pmFPAfileClose (pmFPAfile *file, const pmFPAview *view)
+{
+    if (file->state & PM_FPA_STATE_INACTIVE) {
+        return false;
+    }
+    if (file->state == PM_FPA_STATE_CLOSED) {
+        return true;
+    }
+
+    // is current depth == open depth?
+    pmFPAdepth depth = pmFPAviewDepth (view);
+    if (file->fileDepth != depth) {
+        return false;
+    }
+
+    // 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:
+        fprintf (stderr, "warning: type mismatch\n");
+        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)
+{
+    if (name == NULL) {
+        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 (!file) {
+        return false;
+    }
+    if (state) {
+        file->state &= NOT_U8(PM_FPA_STATE_INACTIVE);
+    } else {
+        file->state |= PM_FPA_STATE_INACTIVE;
+    }
+    return true;
+}
+
+// attempt open, read, write, or close pmFPAfiles in files
+bool pmFPAfileIOChecks (psMetadata *files, const pmFPAview *view, pmFPAfilePlace place)
+{
+    // recipe override values (command-line options):
+    psMetadataItem *item = NULL;
+    psMetadataIterator *iter = psMetadataIteratorAlloc (files, PS_LIST_HEAD, NULL);
+    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+        pmFPAfile *file = item->data.V;
+
+        if (place == PM_FPA_BEFORE) {
+            pmFPAfileRead (file, view);
+            pmFPAfileCreate (file, view);
+        } else {
+            pmFPAfileWrite (file, view);
+            pmFPAfileClose (file, view);
+        }
+    }
+    psFree (iter);
+    return true;
+}
+
+// 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 *pmFPAfileCreateInternal (psMetadata *files, char *name, int Nx, int Ny, int type)
+{
+    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->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)
+{
+    bool status;
+
+    pmFPAfile *file = psMetadataLookupPtr (&status, files, name);
+    if (file == NULL)
+        return false;
+
+    if (file->mode != PM_FPA_MODE_INTERNAL)
+        return false;
+
+    psMetadataRemoveKey (files, name);
+    return true;
+}
+
+// select the readout from the named pmFPAfile; if the named file does not exist,
+pmReadout *pmFPAfileThisReadout (psMetadata *files, const pmFPAview *view, const char *name)
+{
+    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;
+}
+
+// given an already-opened fits file, read the components corresponding
+// to the specified view
+bool pmFPAviewReadFitsImage (const pmFPAview *view, pmFPAfile *file)
+{
+    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) {
+        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) {
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        status = pmCellRead (cell, fits, NULL);
+        return status;
+    }
+    return false;
+
+    // XXX pmReadoutRead, pmReadoutReadSegement disabled for now
+    # if (0)
+
+        if (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
+bool pmFPAviewWriteFitsImage (const pmFPAview *view, pmFPAfile *file)
+{
+
+    pmFPA *fpa = file->fpa;
+    psFits *fits = file->fits;
+
+    // pmFPAWrite takes care of all PHUs as needed
+    if (view->chip == -1) {
+        pmFPAWrite (fpa, fits, NULL, true);
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    // 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
+            psMetadata *outhead = psMetadataCopy (NULL, phu->header);
+            psMetadataAdd (outhead, PS_LIST_TAIL, "EXTEND", PS_DATA_BOOL | PS_META_REPLACE, "this file has extensions", true);
+            psFitsWriteHeaderNotImage (file->fits, outhead);
+            file->phu = phu->header;
+            psTrace ("pmFPAfile", 5, "wrote phu %s (type: %d)\n", file->filename, file->type);
+            psFree (outhead);
+        }
+    }
+
+    if (view->cell == -1) {
+        pmChipWrite (chip, fits, NULL, true);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        pmCellWrite (cell, fits, NULL, true);
+        return true;
+    }
+    return false;
+
+    // XXX disable readout write for now
+    # if (0)
+
+        if (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
+}
+
+// look for the given name on the argument list.
+// returns the file (a view to the one saved on config->files)
+pmFPAfile *pmFPAfileFromArgs (bool *found, pmConfig *config, char *filename, char *argname)
+{
+    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)
+        return NULL;
+    if (infiles->n < 1)
+        return NULL;
+
+    // determine the current format from the header
+    // if no camera has been specified, use the first image as a template for the rest.
+    fits = psFitsOpen (infiles->data[0], "r");
+    phu = psFitsReadHeader (NULL, fits);
+    format = pmConfigCameraFormatFromHeader (config, phu);
+    psFitsClose (fits);
+
+    // build the template fpa, set up the basic view
+    fpa = pmFPAConstruct (config->camera);
+
+    // load the given filerule (from config->camera) and associate it with the fpa
+    // the output file is just a view to the file on config->files
+    file = pmFPAfileDefine (config->files, config->camera, fpa, filename);
+    if (!file) {
+        psErrorStackPrint(stderr, "file %s not defined\n", filename);
+        psFree (fpa);
+        psFree (format);
+        return NULL;
+    }
+
+    // this file is (by virtue of being supplied in the argument list) coming from the fileset
+    psFree (file->filerule);
+    psFree (file->filextra);
+
+    // adjust the rules to identify these files in the file->names data
+    file->filerule = psStringCopy ("@FILES");
+    // XXX this rule does not work in general
+    file->filextra = psStringCopy ("{CHIP.NAME}.{CELL.NAME}");
+
+    // examine the list of input files and validate their cameras
+    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);
+
+        // XXX is this the correct psMD to save the filename?
+        char *name = pmFPAfileNameFromRule (file->filextra, file, view);
+        psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", infiles->data[i]);
+
+        psFree (view);
+        psFree (name);
+        psFree (phu);
+    }
+    psFree (fpa);
+    psFree (format);
+    *found = true;
+
+    return file;
+}
+
+// XXX this this function through, then finish
+# if 0
+// look for the given name on the argument list.
+// returns the file (a view to the one saved on config->files)
+// in this case, each file should correspond to the same view
+// (except at the readout level), of multiple FPAs
+// XXX think this through a bit more:
+//  - do we create multiple input fpas, or assign the same files to the single fpa?
+//  - do we save the multiple pmFPAfiles in the config->files psMD?
+//  - do we save the multiple filenames in the file->names psMD?
+//  - if we have a single FPA and multiple names, we need a method to
+//    turn on a specific name for the I/O actions.  probably true if we
+//    have multiple FPAs as well.
+pmFPAfile *pmFPAfileSetFromArgs (bool *found, pmConfig *config, char *filename, char *argname)
+{
+    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)
+        return NULL;
+    if (infiles->n < 1)
+        return NULL;
+
+    // determine the current format from the header
+    // if no camera has been specified, use the first image as a template for the rest.
+    fits = psFitsOpen (infiles->data[0], "r");
+    phu = psFitsReadHeader (NULL, fits);
+    format = pmConfigCameraFormatFromHeader (config, phu);
+    psFitsClose (fits);
+
+    // build the template fpa, set up the basic view
+    fpa = pmFPAConstruct (config->camera);
+
+    // load the given filerule (from config->camera) and associate it with the fpa
+    // the output file is just a view to the file on config->files
+    // XXX the filenames placed on config->files should include the seq number
+    file = pmFPAfileDefine (config->files, config->camera, fpa, filename);
+    if (!file) {
+        psErrorStackPrint(stderr, "file %s not defined\n", filename);
+        psFree (fpa);
+        psFree (format);
+        return NULL;
+    }
+
+    // this file is (by virtue of being supplied in the argument list) coming from the fileset
+    psFree (file->filerule);
+    psFree (file->filextra);
+
+    // adjust the rules to identify these files in the file->names data
+    file->filerule = psStringCopy ("@FILES");
+    // XXX this rule does not work in general
+    file->filextra = psStringCopy ("{CHIP.NAME}.{CELL.NAME}");
+
+    // examine the list of input files and validate their cameras
+    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);
+
+        // XXX is this the correct psMD to save the filename?
+        char *name = pmFPAfileNameFromRule (file->filextra, file, view);
+        psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", infiles->data[i]);
+
+        psFree (view);
+        psFree (name);
+        psFree (phu);
+    }
+    psFree (fpa);
+    psFree (format);
+    *found = true;
+
+    return file;
+}
+# endif
+
+// create a new output pmFPAfile based on an existing FPA
+pmFPAfile *pmFPAfileFromFPA (pmConfig *config, pmFPA *src, int xBin, int yBin, char *filename)
+{
+    // XXX pmFPAConstruct has many leaks (6919)
+    pmFPA *fpa = pmFPAConstruct (config->camera);
+    pmFPAfile *file = pmFPAfileDefine (config->files, config->camera, fpa, filename);
+    file->src = src; // inherit output elements from this source pmFPA
+    file->xBin = xBin;
+    file->yBin = yBin;
+
+    return file;
+}
+
+// look for the given name on the argument list.
+pmFPAfile *pmFPAfileFromConf (bool *found, pmConfig *config, char *filename, pmFPA *input)
+{
+    psFits *fits = NULL;
+    pmFPAfile *file = NULL;
+    psMetadata *phu = NULL;
+    psMetadata *format = NULL;
+    psArray *infiles = NULL;
+
+    if (*found)
+        return NULL;
+
+    // a camera config is needed (as source of file rule)
+    if (config->camera == NULL) {
+        psErrorStackPrint (stderr, "camera is not defined\n");
+        return NULL;
+    }
+
+    // expect @DETDB in the config filerule
+    file = pmFPAfileDefine (config->files, config->camera, NULL, filename);
+    if (!file) {
+        psErrorStackPrint(stderr, "file %s not defined\n", filename);
+        return NULL;
+    }
+
+    // image names come from the file->name list?
+    if (!strcasecmp (file->filerule, "@FILES"))
+        psAbort ("pmFPAfileFromConfig", "programming error");
+
+    // image needs to come from the detrend database
+    if (!strcasecmp (file->filerule, "@DETDB")) {
+        // char *extra = pmFPAfileNameFromRule (file->filextra, file, view);
+        // psArray *infiles = pmDetrendSelect (extra, input);
+        psAbort ("pmFPAfileFromConfig", "@DETDB not yet defined");
+    } else {
+        infiles = psArrayAlloc(1);
+        infiles->n = 1;
+        infiles->data[0] = psStringCopy (file->filerule);
+    }
+    if (infiles == NULL)
+        return NULL;
+    if (infiles->n < 1) {
+        psFree (infiles);
+        return NULL;
+    }
+
+    // determine the current format from the header
+    // if no camera has been specified, use the first image as a template for the rest.
+    fits = psFitsOpen (infiles->data[0], "r");
+    phu = psFitsReadHeader (NULL, fits);
+    format = pmConfigCameraFormatFromHeader (config, phu);
+    psFitsClose (fits);
+
+    // build the template fpa, set up the basic view
+    file->fpa = pmFPAConstruct (config->camera);
+
+    // this file is (by virtue of being supplied in the argument list) coming from the fileset
+    psFree (file->filerule);
+    psFree (file->filextra);
+
+    // adjust the rules to identify these files in the file->names data
+    file->filerule = psStringCopy ("@FILES");
+    // XXX this rule does not work in general
+    file->filextra = psStringCopy ("{CHIP.NAME}.{CELL.NAME}");
+
+    // examine the list of input files and validate their cameras
+    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 (file->fpa, phu, format);
+
+        // XXX is this the correct psMD to save the filename?
+        char *name = pmFPAfileNameFromRule (file->filextra, file, view);
+        psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", infiles->data[i]);
+
+        psFree (view);
+        psFree (name);
+        psFree (phu);
+    }
+    psFree (format);
+    psFree (infiles);
+    *found = true;
+    return file;
+}
+
+bool pmFPAfileAddFileNames (psMetadata *files, char *name, char *value, int mode)
+{
+
+    // add the output names to the output-type files
+    psMetadataItem *item = NULL;
+    psMetadataIterator *iter = psMetadataIteratorAlloc (files, PS_LIST_HEAD, NULL);
+    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+        pmFPAfile *file = item->data.V;
+
+        if (file->mode == mode) {
+            psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", value);
+        }
+    }
+    psFree (iter);
+    return true;
+}
+
+// select the rule from the camera configuration, perform substitutions as needed
+char *pmFPAfileNameFromRule (char *rule, pmFPAfile *file, const pmFPAview *view)
+{
+
+    char *newName = NULL;     // destination for resulting name
+
+    newName = psStringCopy (rule);
+
+    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, "{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, "{EXTNAME}") != NULL) {
+        pmHDU *hdu = pmFPAviewThisHDU (view, file->fpa);
+        if (hdu->extname != NULL) {
+            newName = psStringSubstitute (newName, hdu->extname, "{EXTNAME}");
+        }
+    }
+    if (strstr (newName, "{OUTPUT}") != NULL) {
+        char *name = psMetadataLookupStr (NULL, file->names, "OUTPUT");
+        if (name != NULL) {
+            newName = psStringSubstitute (newName, name, "{OUTPUT}");
+        }
+    }
+    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)
+{
+    // pmFPAWrite takes care of all PHUs as needed
+    if (view->chip == -1) {
+        pmFPACopy (out, in);
+        return true;
+    }
+    if (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) {
+        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;
+    }
+    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, psMetadata *format, int xBin, int yBin, const pmFPAview *view)
+{
+    // pmFPAWrite takes care of all PHUs as needed
+    if (view->chip == -1) {
+        pmFPAAddSourceFromView (out, view, format);
+        pmFPACopyStructure (out, in, xBin, yBin);
+        return true;
+    }
+    if (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) {
+        pmFPAAddSourceFromView (out, view, format);
+        pmChipCopyStructure (outChip, inChip, xBin, yBin);
+        return true;
+    }
+    if (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) {
+        pmFPAAddSourceFromView (out, view, format);
+        pmCellCopyStructure (outCell, inCell, xBin, yBin);
+        return true;
+    }
+    return false;
+
+    // XXX add readout / segment equivalents
+}
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAfile.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAfile.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAfile.h	(revision 6961)
@@ -0,0 +1,160 @@
+/** @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-04-19 20:30:49 $
+*
+*  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,
+} 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?
+
+    pmFPAdepth fileDepth;  // what depth in the FPA hierarchy represents a unique file?
+    pmFPAdepth dataDepth;  // at what depth 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
+
+    // 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 *format;
+}
+pmFPAfile;
+
+// allocate an empty pmFPAfile structure
+pmFPAfile *pmFPAfileAlloc ();
+
+// load the pmFPAfile information from the camera configuration data
+pmFPAfile *pmFPAfileDefine (psMetadata *files, psMetadata *camera, pmFPA *fpa, char *name);
+
+// load the pmFPAfile information from the camera configuration data, constructing the needed fpa structure
+// XXX deprecate this function?
+// pmFPAfile *pmFPAfileConstruct (psMetadata *files, psMetadata *format, psMetadata *camera, char *name);
+
+// open the real file corresponding to the given pmFPAfile appropriate to the current view
+bool pmFPAfileOpen (pmFPAfile *file, const pmFPAview *view);
+
+// read from the real file corresponding to the given pmFPAfile for the current view
+bool pmFPAfileRead (pmFPAfile *file, const pmFPAview *view);
+
+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);
+
+// close the real file corresponding to the given pmFPAfile appropriate to the current view
+bool pmFPAfileClose (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 (psMetadata *files, const pmFPAview *view, pmFPAfilePlace place);
+
+// return an image corresponding to the current readout, from the specified file.  if the pmFPAfile does not
+// exist, construct the image using the given size and type (save it in a pmFPAfile??)
+// psImage *pmFPAfileReadoutImage (psMetadata *files, const pmFPAview *view, char *name, int Nx, int Ny, int type);
+
+// select the readout from the named pmFPAfile; if the named file does not exist,
+pmReadout *pmFPAfileThisReadout (psMetadata *files, const pmFPAview *view, const char *name);
+
+// 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 *pmFPAfileCreateInternal (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);
+
+// 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);
+
+// look for the given argname on the argument list.  find the give filename from the file rules
+pmFPAfile *pmFPAfileFromArgs (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 *pmFPAfileFromConf (bool *found, pmConfig *config, char *filename, pmFPA *input);
+
+// create a new output pmFPAfile based on an existing FPA
+pmFPAfile *pmFPAfileFromFPA (pmConfig *config, pmFPA *src, int xBin, int yBin, char *filename);
+
+// 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, psMetadata *format, int xBin, int yBin, const pmFPAview *view);
+
+# endif
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAview.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAview.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAview.c	(revision 6961)
@@ -0,0 +1,267 @@
+/** @file  pmFPAview.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:01:04 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#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)
+{
+    if (view == NULL)
+        return;
+    return;
+}
+
+pmFPAview *pmFPAviewAlloc (int nRows)
+{
+    pmFPAview *view = psAlloc (sizeof(pmFPAview));
+    psMemSetDeallocator (view, (psFreeFunc) pmFPAviewFree);
+
+    view->chip    = -1;
+    view->cell    = -1;
+    view->readout = -1;
+    view->iRows   =  0;
+    view->nRows   = nRows;
+    return (view);
+}
+
+pmFPAdepth pmFPAviewDepth (const pmFPAview *view)
+{
+
+    if (view->chip < 0) {
+        return PM_FPA_DEPTH_FPA;
+    }
+    if (view->cell < 0) {
+        return PM_FPA_DEPTH_CHIP;
+    }
+    if (view->readout < 0) {
+        return PM_FPA_DEPTH_CELL;
+    }
+    return PM_FPA_DEPTH_READOUT;
+}
+
+pmChip *pmFPAviewThisChip (const pmFPAview *view, pmFPA *fpa)
+{
+
+    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)
+{
+    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)
+{
+
+    if (view->cell < 0) {
+        return NULL;
+    }
+
+    pmChip *chip = pmFPAviewThisChip (view, fpa);
+    if (chip == NULL) {
+        return 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)
+{
+
+    pmChip *chip = pmFPAviewThisChip (view, fpa);
+    if (chip == NULL) {
+        return 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)
+{
+
+    if (view->readout < 0) {
+        return NULL;
+    }
+
+    pmCell *cell = pmFPAviewThisCell (view, fpa);
+    if (cell == NULL) {
+        return 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)
+{
+
+    pmCell *cell = pmFPAviewThisCell (view, fpa);
+    if (cell == NULL) {
+        return 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)
+{
+    // 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 NULL;
+}
+
+pmHDU *pmFPAviewThisPHU (const pmFPAview *view, pmFPA *fpa)
+{
+    // 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->phu)
+            return hdu;
+        return NULL;
+    }
+    if (view->cell < 0) {
+        chip = pmFPAviewThisChip (view, fpa);
+        hdu  = pmHDUFromChip (chip);
+        if (hdu->phu)
+            return hdu;
+        new.chip = -1;
+        hdu = pmFPAviewThisPHU (&new, fpa);
+        return hdu;
+    }
+    if (view->readout < 0) {
+        cell = pmFPAviewThisCell (view, fpa);
+        hdu  = pmHDUFromCell (cell);
+        if (hdu->phu)
+            return hdu;
+        new.cell = -1;
+        hdu = pmFPAviewThisPHU (&new, fpa);
+        return hdu;
+    }
+    return NULL;
+}
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAview.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAview.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmFPAview.h	(revision 6961)
@@ -0,0 +1,70 @@
+/** @file  pmFPAview.h
+*
+*  @brief Tools to manipulate the FPA structure elements.
+*
+*  @ingroup AstroImage
+*
+*  @author EAM, IfA
+*
+*  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+*  @date $Date: 2006-04-17 18:01:04 $
+*
+*  Copyright 2004-2005 Institute for Astronomy, University of Hawaii
+*/
+
+#ifndef PM_FPA_VIEW_H
+#define PM_FPA_VIEW_H
+
+#include "pmFPA.h"
+
+/// @addtogroup AstroImage
+/// @{
+
+// depth of interest
+typedef enum {
+    PM_FPA_DEPTH_NONE,
+    PM_FPA_DEPTH_FPA,
+    PM_FPA_DEPTH_CHIP,
+    PM_FPA_DEPTH_CELL,
+    PM_FPA_DEPTH_READOUT,
+} pmFPAdepth;
+
+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);
+
+// determine the current view depth
+pmFPAdepth pmFPAviewDepth (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/rel11_ifa_pre0/psModules/src/astrom/pmHDU.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmHDU.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmHDU.c	(revision 6961)
@@ -0,0 +1,225 @@
+#include <stdio.h>
+#include <assert.h>
+
+#include "pslib.h"
+#include "pmFPA.h"
+#include "psAdditionals.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 (strcasecmp(hdu->extname, "PHU") == 0 || hdu->phu) {
+        if (! psFitsMoveExtNum(fits, 0, false)) {
+            psError(PS_ERR_IO, false, "Unable to move to primary header!\n");
+            return false;
+        }
+        hdu->phu = true;
+        return true;
+    }
+
+    if (! psFitsMoveExtName(fits, hdu->extname)) {
+        psError(PS_ERR_IO, false, "Unable to move to extension %s\n", hdu->extname);
+        return false;
+    }
+    // Now, just in case for some reason the PHU has an extension name that we've moved to....
+    if (psFitsGetExtNum(fits) == 0) {
+        hdu->phu = true;
+    } else {
+        hdu->phu = 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->phu = true;
+        hdu->extname = psStringCopy("PHU");
+    } else {
+        if (strcasecmp(extname, "PHU") == 0) {
+            hdu->phu = true;
+        } else {
+            hdu->phu = 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
+                    )
+{
+    assert(hdu);
+    assert(fits);
+
+    // Move to the appropriate extension
+    psTrace(__func__, 5, "Moving to extension %s...\n", hdu->extname);
+    if (!hduMove(hdu, fits)) {
+        return false;
+    }
+
+    if (!hdu->header) {
+        psTrace(__func__, 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
+              )
+{
+    assert(hdu);
+    assert(fits);
+
+    // Read the header; includes the move
+    if (!pmHDUReadHeader(hdu, fits)) {
+        return false;
+    }
+
+    #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(__func__, 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
+               )
+{
+    assert(hdu);
+    assert(fits);
+
+    psTrace(__func__, 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 (strcasecmp(extname, "PHU") == 0 && 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) {
+        // Tell CFITSIO there's nothing there
+        psMetadataItem *naxis = psMetadataLookup(hdu->header, "NAXIS");
+        if (naxis) {
+            naxis->data.S32 = 0;
+        }
+
+        if (!psFitsWriteHeader(hdu->header, fits)) {
+            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(__func__, 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/rel11_ifa_pre0/psModules/src/astrom/pmHDU.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmHDU.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmHDU.h	(revision 6961)
@@ -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 phu;                           // Is this the FITS Primary Header Unit
+    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/rel11_ifa_pre0/psModules/src/astrom/pmHDUUtils.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmHDUUtils.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmHDUUtils.c	(revision 6961)
@@ -0,0 +1,60 @@
+#include <stdio.h>
+#include "pmFPA.h"
+#include "pmHDU.h"
+#include "pmHDUUtils.h"
+
+
+pmHDU *pmHDUFromFPA(pmFPA *fpa          // FPA for which to find HDU
+                   )
+{
+    return fpa->hdu;
+}
+
+pmHDU *pmHDUFromChip(pmChip *chip       // Chip for which to find HDU
+                    )
+{
+    pmHDU *hdu = chip->hdu;             // The HDU information
+    if (!hdu) {
+        hdu = pmHDUFromFPA(chip->parent); // Grab HDU info from the FPA
+    }
+
+    return hdu;
+}
+
+pmHDU *pmHDUFromCell(pmCell *cell       // Cell for which to find HDU
+                    )
+{
+    pmHDU *hdu = cell->hdu;             // The HDU information
+    if (!hdu) {
+        hdu = pmHDUFromChip(cell->parent); // Grab HDU info from the chip
+    }
+
+    return hdu;
+}
+
+pmHDU *pmHDUFromReadout (pmReadout *readout)
+{
+
+    pmCell *cell = readout->parent; // cell containing this readout;
+    pmHDU *hdu = pmHDUFromCell (cell);
+    return hdu;
+}
+
+// Get the lowest HDU
+pmHDU *pmHDUGetLowest(pmFPA *fpa, // The FPA
+                      pmChip *chip, // The chip, or NULL
+                      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;
+}
+
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmHDUUtils.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmHDUUtils.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmHDUUtils.h	(revision 6961)
@@ -0,0 +1,24 @@
+#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(pmFPA *fpa, // The FPA
+                      pmChip *chip, // The chip, or NULL
+                      pmCell *cell // The cell, or NULL
+                     );
+
+// Find the HDU in the FPA hierarchy
+pmHDU *pmHDUFromFPA(pmFPA *fpa          // FPA for which to find HDU
+                   );
+pmHDU *pmHDUFromChip(pmChip *chip       // Chip for which to find HDU
+                    );
+pmHDU *pmHDUFromCell(pmCell *cell       // Cell for which to find HDU
+                    );
+
+pmHDU *pmHDUFromReadout (pmReadout *readout  // Readout for which to find HDU
+                        );
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmReadout.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmReadout.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmReadout.c	(revision 6961)
@@ -0,0 +1,111 @@
+#include <stdio.h>
+#include "pslib.h"
+#include "pmFPA.h"
+#include "pmMaskBadPixels.h"
+#include "pmHDUUtils.h"
+
+// Get the bias images for a readout, using the CELL.BIASSEC
+psList *pmReadoutGetBias(pmReadout *readout // Readout for which to get the bias sections
+                        )
+{
+    pmCell *cell = readout->parent;     // The parent cell
+    psList *sections = (psList*)psMetadataLookupPtr(NULL, cell->concepts, "CELL.BIASSEC"); // CELL.BIASSEC
+
+    psImage *image = readout->image;    // The image that contains both the biassec and the trimsec
+
+    psList *images = psListAlloc(NULL); // List of images from bias sections
+    psListIterator *sectionsIter = psListIteratorAlloc(sections, PS_LIST_HEAD, true); // Iterator
+    psRegion *region = NULL;            // Bias region from list
+    while ((region = psListGetAndIncrement(sectionsIter))) {
+        psImage *bias = psMemIncrRefCounter(psImageSubset(image, *region)); // Image from bias section
+        psListAdd(images, PS_LIST_TAIL, bias);
+        psFree(bias);
+    }
+    psFree(sectionsIter);
+
+    return images;
+}
+
+bool pmReadoutSetWeights(pmReadout *readout)
+{
+
+    pmCell *cell = readout->parent;
+
+    float gain = psMetadataLookupF32(NULL, cell->concepts, "CELL.GAIN"); // Cell gain
+    float readnoise = psMetadataLookupF32(NULL, cell->concepts, "CELL.READNOISE"); // Cell read noise
+    psRegion *trimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC"); // Trim section
+    float saturation = psMetadataLookupF32(NULL, cell->concepts, "CELL.SATURATION"); // Saturation level
+    float bad = psMetadataLookupF32(NULL, cell->concepts, "CELL.BAD"); // Bad level
+
+    pmHDU *hdu = pmHDUFromCell (cell);
+
+    psArray *pixels = hdu->images;      // Array of images
+    psArray *weights = hdu->weights;    // Array of weight images
+    psArray *masks = hdu->masks;        // Array of mask images
+    // Generate the weights and masks if required
+    if (! weights) {
+        weights = psArrayAlloc(pixels->n);
+        weights->n = pixels->n;
+        for (int i = 0; i < pixels->n; i++) {
+            psImage *image = pixels->data[i];
+            weights->data[i] = psImageAlloc(image->numCols, image->numRows, PS_TYPE_F32);
+            psImageInit(weights->data[i], 0.0);
+        }
+        hdu->weights = weights;
+    }
+    if (! masks) {
+        masks = psArrayAlloc(pixels->n);
+        masks->n = pixels->n;
+        for (int i = 0; i < pixels->n; i++) {
+            psImage *image = pixels->data[i];
+            masks->data[i] = psImageAlloc(image->numCols, image->numRows, PS_TYPE_U8);
+            psImageInit(masks->data[i], 0);
+        }
+        hdu->masks = masks;
+    }
+
+    // Set the pixels
+    psArray *readouts = cell->readouts; // Array of readouts
+    for (int i = 0; i < readouts->n; i++) {
+        pmReadout *readout = readouts->data[i]; // The readout of interest
+
+        if (! readout->weight) {
+            readout->weight = psMemIncrRefCounter(psImageSubset(weights->data[i], *trimsec));
+        }
+        if (! readout->mask) {
+            readout->mask = psMemIncrRefCounter(psImageSubset(masks->data[i], *trimsec));
+        }
+
+        // Set up the mask
+        psImage *image = readout->image;// Pixels
+        psImage *mask = readout->mask;  // Mask image
+        for (int i = 0; i < image->numRows; i++) {
+            for (int j = 0; j < image->numCols; j++) {
+                if (image->data.F32[i][j] >= saturation) {
+                    mask->data.U8[i][j] = PM_MASK_SAT;
+                }
+                if (image->data.F32[i][j] <= bad) {
+                    mask->data.U8[i][j] = PM_MASK_BAD;
+                }
+            }
+        }
+
+        psImage *weight = readout->weight;  // Mask image
+        float rnoise = PS_SQR(readnoise/gain);
+        for (int i = 0; i < image->numRows; i++) {
+            for (int j = 0; j < image->numCols; j++) {
+                if (!mask->data.U8[i][j]) {
+                    weight->data.F32[i][j] = image->data.F32[i][j]/ gain + rnoise;
+                }
+            }
+        }
+
+        // Set weight image to the variance = g*f + rn^2
+        // psBinaryOp(readout->weight, image, "/", psScalarAlloc(gain, PS_TYPE_F32));
+        // psBinaryOp(readout->weight, readout->weight, "+",
+        // psScalarAlloc(readnoise*readnoise/gain/gain, PS_TYPE_F32));
+    }
+
+    return true;
+}
+
Index: /tags/rel11_ifa_pre0/psModules/src/astrom/pmReadout.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/astrom/pmReadout.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/astrom/pmReadout.h	(revision 6961)
@@ -0,0 +1,13 @@
+#ifndef PM_READOUT_H
+#define PM_READOUT_H
+
+#include "pslib.h"
+#include "pmFPA.h"
+
+// Get the bias sections for a specific readout
+psList *pmReadoutGetBias(pmReadout *readout // Readout for which to get the bias sections
+                        );
+
+bool pmReadoutSetWeights(pmReadout *readout);
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/camera/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/camera/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/camera/.cvsignore	(revision 6961)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/rel11_ifa_pre0/psModules/src/camera/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/camera/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/camera/Makefile.am	(revision 6961)
@@ -0,0 +1,22 @@
+noinst_LTLIBRARIES = libpsmodulecamera.la
+
+libpsmodulecamera_la_CPPFLAGS = $(SRCINC) $(PSMODULE_CFLAGS)
+libpsmodulecamera_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulecamera_la_SOURCES  = \
+	pmChipMosaic.c \
+	pmFPAConceptsGet.c \
+	pmFPAConceptsSet.c \
+	pmFPAConstruct.c \
+	pmFPAMorph.c \
+	pmFPARead.c \
+	pmFPAWrite.c
+
+psmoduleincludedir = $(includedir)
+psmoduleinclude_HEADERS = \
+	pmChipMosaic.h \
+	pmFPAConceptsGet.h \
+	pmFPAConceptsSet.h \
+	pmFPAConstruct.h \
+	pmFPAMorph.h \
+	pmFPARead.h \
+	pmFPAWrite.h
Index: /tags/rel11_ifa_pre0/psModules/src/config/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/config/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/config/.cvsignore	(revision 6961)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/rel11_ifa_pre0/psModules/src/config/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/config/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/config/Makefile.am	(revision 6961)
@@ -0,0 +1,10 @@
+noinst_LTLIBRARIES = libpsmoduleconfig.la
+
+libpsmoduleconfig_la_CPPFLAGS = $(SRCINC) $(PSMODULE_CFLAGS)
+libpsmoduleconfig_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmoduleconfig_la_SOURCES  = \
+    pmConfig.c
+
+psmoduleincludedir = $(includedir)
+psmoduleinclude_HEADERS = \
+    pmConfig.h
Index: /tags/rel11_ifa_pre0/psModules/src/config/pmConfig.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/config/pmConfig.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/config/pmConfig.c	(revision 6961)
@@ -0,0 +1,681 @@
+/** @file  pmConfig.h
+ *
+ *  @author PAP, IfA
+ *
+ *  @version $Revision: 1.12 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-22 03:20:16 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <glob.h>
+#include "pslib.h"
+#include "pmConfig.h"
+#include "psMetadataItemCompare.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->recipes);
+    psFree(config->arguments);
+    psFree(config->database);
+}
+
+pmConfig *pmConfigAlloc(void)
+{
+    pmConfig *config = psAlloc(sizeof(pmConfig));
+    (void)psMemSetDeallocator(config, (psFreeFunc)configFree);
+
+    // Initialise
+    config->site = NULL;
+    config->camera = NULL;
+    config->recipes = NULL;
+    config->arguments = NULL;
+    config->database = NULL;
+
+    // the file structure is used to carry pmFPAfiles
+    config->files = psMetadataAlloc ();
+    return config;
+}
+
+void pmConfigDone(void)
+{
+    psFree(configPath);
+}
+
+
+/** 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
+{
+    char *realName = NULL;
+    unsigned int numBadLines = 0;
+    struct stat filestat;
+
+    psLogMsg(__func__, PS_LOG_INFO, "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, false, "Cannot find %s configuration file in path\n", description);
+        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, false, "Cannot find %s configuration file in path\n", description);
+    return false;
+
+found:
+    *config = psMetadataConfigParse(NULL, &numBadLines, realName, true);
+    if (numBadLines > 0) {
+        psLogMsg(__func__, PS_LOG_WARN, "%d bad lines in %s configuration file (%s)\n",
+                 description, realName);
+    }
+    if (!*config) {
+        psError(PS_ERR_IO, false, "Unable to read %s configuration from %s\n",
+                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_INT_POSITIVE(*argc, false);
+    PS_ASSERT_PTR_NON_NULL(argv, false);
+
+    pmConfig *config = pmConfigAlloc(); // 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(*argc, 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, argc, argv);
+        if (argNum >= *argc) {
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "-site command-line switch provided without the required filename --- ignored.\n");
+        } else {
+            siteName = psStringCopy(argv[argNum]);
+            psArgumentRemove(argNum, argc, 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;
+    }
+    psFree(siteName);
+
+    // define the config-file search path (configPath)
+    if (configPath) {
+        psFree(configPath);
+        configPath = NULL;
+    }
+    char *path = psMetadataLookupStr(NULL, config->site, "PATH");
+    if (path) {
+        psList *list = psStringSplit(path, ":");
+        configPath = psListToArray(list);
+        psFree(list);
+    }
+
+    // Next, we do a similar thing for the camera configuration file.  The
+    // file is read and parsed into psMetadata struct "camera".
+    //
+    argNum = psArgumentGet(*argc, argv, "-camera");
+    if (argNum > 0) {
+        psArgumentRemove(argNum, argc, argv);
+        if (argNum >= *argc) {
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "-camera command-line switch provided without the required filename --- ignored.\n");
+        } else {
+            psArgumentRemove(argNum, argc, argv);
+            readConfig(&config->camera, argv[argNum], "camera");
+        }
+    }
+
+    // Load the recipes from the camera file, if appropriate
+    if (! config->recipes && config->camera) {
+        pmConfigReadRecipes(config);
+    }
+
+
+    //
+    // 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(__func__, 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(__func__, 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(__func__, 7, "Setting log format to %s\n", logFormat);
+        psLogSetFormat(logFormat);
+    }
+
+
+    //
+    // 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) {
+        // XXX: Only stdout and stderr are provided for now; this section should be
+        // expanded in the future to do files, and perhaps even sockets.
+        int logFD = STDIN_FILENO; // a known invalid value
+        if (!strcasecmp(logDest, "STDOUT")) {
+            logFD = STDOUT_FILENO;
+        }
+        if (!strcasecmp(logDest, "STDERR")) {
+            logFD = STDERR_FILENO;
+        }
+        if (logFD == STDIN_FILENO) {
+            psLogMsg(__func__, PS_LOG_WARN, "Only STDERR and STDOUT currently supported as a log destination.\n");
+            logFD = STDERR_FILENO;
+        }
+        psTrace(__func__, 7, "Setting log destination to STDOUT.\n");
+        psLogSetDestination(logFD);
+    }
+
+    //
+    // 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(__func__, 7, "Setting trace level for %s to %d\n", traceItem->name, traceItem->data.S32);
+            psTraceSetLevel(traceItem->name, traceItem->data.S32);
+        }
+        psFree(traceIter);
+    }
+
+    //
+    // 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(argc, argv);
+    // XXX: substitute the string for the default log level for "2".
+    if (2 == psLogGetLevel()) {
+        psLogSetLevel(saveLogLevel);
+    }
+
+    return config;
+}
+
+
+bool pmConfigValidateCameraFormat(
+    const psMetadata *cameraFormat,
+    const psMetadata *header)
+{
+    // 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(__func__, 5, "Can't find %s\n", ruleItem->name);
+            return false;
+        }
+
+        // Check to see if the rule works
+        if (! psMetadataItemCompare(headerItem, ruleItem)) {
+            psTrace(__func__, 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);
+
+    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(__func__, 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
+)
+{
+    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, false, "Unable to find CAMERAS in the configuration.\n");
+            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(__func__, 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(__func__, 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);
+            }
+            psFree(testCamera);
+        } // Done looking at all cameras
+        psFree(camerasIter);
+
+        if (! config->camera) {
+            psError(PS_ERR_IO, true, "Unable to find a camera that matches input FITS header!\n");
+            return NULL;
+        }
+
+        // Now we have the camera, we can read the recipes
+        if (!config->recipes) {
+            pmConfigReadRecipes(config);
+        }
+
+        return format;
+    }
+
+    // Otherwise, try the specific camera
+    if (! formatFromHeader(&format, config->camera, header, "specified camera")) {
+        psError(PS_ERR_IO, true, "Unable to find a format with the specified camera that matches the "
+                "given header.\n");
+        return NULL;
+    }
+    return format;
+}
+
+bool pmConfigReadRecipes(
+    pmConfig *config
+)
+{
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_PTR_NON_NULL(config->camera, false);
+
+    if (!config->recipes) {
+        config->recipes = psMetadataAlloc();
+    }
+
+    bool mdok = true;                   // Status of MD lookup
+    psMetadata *recipes = psMetadataLookupMD(&mdok, config->camera, "RECIPES"); // The list of recipes
+    if (! mdok || ! recipes) {
+        psLogMsg(__func__, PS_LOG_WARN, "RECIPES in the camera configuration file is not of type METADATA\n");
+        return false;
+    }
+    // Go through the recipes and load each one
+    psMetadataIterator *recipesIter = psMetadataIteratorAlloc(recipes, PS_LIST_HEAD, NULL); // Iterator
+    psMetadataItem *recipesItem = NULL; // Item from iteration
+    while ((recipesItem = psMetadataGetAndIncrement(recipesIter))) {
+        if (recipesItem->type != PS_DATA_STRING) {
+            psLogMsg(__func__, PS_LOG_WARN, "Filename for recipe %s isn't of type STR --- ignored.\n",
+                     recipesItem->name);
+            continue;
+        }
+
+        psMetadata *recipe = NULL;      // Recipe from file
+        if (readConfig(&recipe, recipesItem->data.V, "recipe")) {
+            psMetadataAdd(config->recipes, PS_LIST_TAIL, recipesItem->name,
+                          PS_DATA_METADATA | PS_META_REPLACE, recipesItem->comment, recipe);
+        }
+        psFree(recipe);                 // Drop reference
+    }
+    psFree(recipesIter);
+
+    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(
+    psMetadata *site)
+{
+    #ifdef OMIT_PSDB
+    return NULL;
+    #else
+
+    PS_ASSERT_PTR_NON_NULL(site, NULL);
+    psBool mdStatus01 = false;
+    psBool mdStatus02 = false;
+    psBool mdStatus03 = false;
+
+    // XXX leaky strings
+    psString dbServer = psMetadataLookupStr(&mdStatus01, site, "DBSERVER");
+    psString dbUsername = psMetadataLookupStr(&mdStatus02, site, "DBUSER");
+    psString dbPassword = psMetadataLookupStr(&mdStatus03, site, "DBPASSWORD");
+    psString dbName = psMetadataLookupStr(&mdStatus01, site, "DBNAME");
+    if (!(mdStatus01 & mdStatus02 & mdStatus03)) {
+        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));
+    #endif
+}
+
+
+bool pmConfigConformHeader(psMetadata *header, // Header to conform
+                           const psMetadata *format // Camera format
+                          )
+{
+    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, false, "Unable to find RULE in camer 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 (int *argc, char **argv, char *file, char *list)
+{
+
+    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 (may be a glob: "file*.fits")
+    if ((Narg = psArgumentGet (*argc, argv, file))) {
+        glob_t globList;
+        psArgumentRemove (Narg, argc, argv);
+        globList.gl_offs = 0;
+        glob (argv[Narg], 0, NULL, &globList);
+        for (int i = 0; i < globList.gl_pathc; i++) {
+            char *filename = psStringCopy (globList.gl_pathv[i]);
+            psArrayAdd (input, 16, filename);
+            psFree (filename);
+        }
+        psArgumentRemove (Narg, argc, argv);
+    }
+
+    // load the list from the supplied text file
+    if ((Narg = psArgumentGet (*argc, argv, list))) {
+        int nItems;
+        char line[1024]; // XXX limits the list lines to 1024 chars
+        char word[1024];
+        char *filename;
+
+        psArgumentRemove (Narg, argc, argv);
+        FILE *f = fopen (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, argc, argv);
+    }
+
+    return input;
+}
+
+bool pmConfigFileSetsMD (psMetadata *metadata, int *argc, char **argv, char *name, char *file, char *list)
+{
+
+    psArray *files = pmConfigFileSets (argc, argv, file, list);
+    if (files->n == 0) {
+        psFree (files);
+        return false;
+    }
+
+    psMetadataAddPtr (metadata, PS_LIST_TAIL, name,  PS_DATA_ARRAY, "", files);
+    psFree (files);
+    return true;
+}
Index: /tags/rel11_ifa_pre0/psModules/src/config/pmConfig.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/config/pmConfig.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/config/pmConfig.h	(revision 6961)
@@ -0,0 +1,121 @@
+/** @file  pmConfig.h
+ *
+ *  @author PAP, IfA
+ *
+ *  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-22 03:20:16 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+#ifndef PM_CONFIG_H
+#define PM_CONFIG_H
+
+#include "pslib.h"
+
+// Configuration information
+typedef struct
+{
+    psMetadata *site;                   // Site configuration
+    psMetadata *camera;                 // Camera specification
+    psMetadata *recipes;                // Recipes for processing
+    psMetadata *arguments;              // Command-line arguments
+    psMetadata *files;                  // pmFPAfiles used for analysis
+    //    psMetadata *format;   // camera format for primary image
+    psDB *database;                     // Database handle
+}
+pmConfig;
+
+pmConfig *pmConfigAlloc(void);
+
+// 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
+);
+
+
+/** pmConfigRecipeFromCamera
+ *
+ * pmConfigRecipeFromCamera shall load the recipes from the list of known recipes contained in the camera
+ * configuration.
+ *
+ */
+bool pmConfigReadRecipes(
+    pmConfig *config
+);
+
+/** 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(
+    psMetadata *site
+);
+
+/** 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 (int *argc, char **argv, char *file, char *list);
+bool pmConfigFileSetsMD (psMetadata *metadata, int *argc, char **argv, char *name, char *file, char *list);
+
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/detrend/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/detrend/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/detrend/.cvsignore	(revision 6961)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/rel11_ifa_pre0/psModules/src/detrend/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/detrend/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/detrend/Makefile.am	(revision 6961)
@@ -0,0 +1,20 @@
+noinst_LTLIBRARIES = libpsmoduledetrend.la
+
+libpsmoduledetrend_la_CPPFLAGS = $(SRCINC) $(PSMODULE_CFLAGS)
+libpsmoduledetrend_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmoduledetrend_la_SOURCES  = \
+	pmFlatField.c \
+	pmFringeStats.c \
+	pmMaskBadPixels.c \
+	pmNonLinear.c
+
+psmoduleincludedir = $(includedir)
+psmoduleinclude_HEADERS = \
+	pmFlatField.h \
+	pmFlatFieldErrors.h \
+	pmFringeStats.h \
+	pmMaskBadPixelsErrors.h \
+	pmMaskBadPixels.h \
+	pmNonLinear.h
+
+EXTRA_DIST = pmFlatFieldErrors.dat pmMaskBadPixelsErrors.dat
Index: /tags/rel11_ifa_pre0/psModules/src/detrend/pmFlatField.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/detrend/pmFlatField.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/detrend/pmFlatField.c	(revision 6961)
@@ -0,0 +1,191 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+/** @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.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:10:08 $
+ *
+ *  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 "pmFlatField.h"
+#include "pmMaskBadPixels.h"
+#include "pmFlatFieldErrors.h"
+
+
+bool pmFlatField(pmReadout *in, const pmReadout *flat)
+{
+    int i = 0;
+    int j = 0;
+    int totOffCol = 0;
+    int totOffRow = 0;
+    psElemType inType;
+    psElemType flatType;
+    psElemType maskType;
+
+    // Check for nulls
+    if (in == NULL) {
+        return true;       // Readout may not have data in it
+    } else if(flat==NULL) {
+        psError( PS_ERR_BAD_PARAMETER_NULL, true,
+                 PS_ERRORTEXT_pmFlatField_NULL_FLAT_READOUT);
+        return false;
+    }
+
+    psImage *inImage   = in->image;     // Input image
+    psImage *inMask    = in->mask;      // Mask for input image
+    psImage *flatImage = flat->image;   // Flat-field image
+
+    // 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");
+
+    if (inImage == NULL) {
+        psError( PS_ERR_BAD_PARAMETER_NULL, true,
+                 PS_ERRORTEXT_pmFlatField_NULL_INPUT_IMAGE);
+        return false;
+    } else if(flatImage == NULL) {
+        psError( PS_ERR_BAD_PARAMETER_NULL, true,
+                 PS_ERRORTEXT_pmFlatField_NULL_FLAT_IMAGE);
+        return false;
+    }
+
+    // Check input image and its mask are not larger than flat image
+
+    if (inImage->numRows>flatImage->numRows || inImage->numCols>flatImage->numCols) {
+        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
+                 PS_ERRORTEXT_pmFlatField_SIZE_INPUT_IMAGE,
+                 inImage->numRows, inImage->numCols, flatImage->numRows, flatImage->numCols);
+        return false;
+    }
+    if (inMask && (inMask->numRows > flatImage->numRows || inMask->numCols > flatImage->numCols)) {
+        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
+                 PS_ERRORTEXT_pmFlatField_SIZE_MASK_IMAGE,
+                 inMask->numRows, inMask->numCols, flatImage->numRows, flatImage->numCols);
+        return false;
+    }
+
+    // Determine total offset based on image offset with chip offset
+    totOffCol = inImage->col0 + y0in - flatImage->col0 - y0flat;
+    totOffRow = inImage->row0 + x0in - flatImage->row0 - x0flat;
+
+    // Check that offsets are within image limits
+    if(totOffRow>=flatImage->numRows || totOffCol>=flatImage->numCols) {
+        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
+                 PS_ERRORTEXT_pmFlatField_OFFSET_FLAT_IMAGE,
+                 totOffRow, totOffCol, flatImage->numRows, flatImage->numCols);
+        return false;
+    } else if(totOffRow>=inImage->numRows || totOffCol>=inImage->numCols) {
+        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
+                 PS_ERRORTEXT_pmFlatField_OFFSET_INPUT_IMAGE,
+                 totOffRow, totOffCol, inImage->numRows, inImage->numCols);
+        return false;
+    } else if(inMask && (totOffRow>=inMask->numRows || totOffCol>=inMask->numCols)) {
+        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
+                 PS_ERRORTEXT_pmFlatField_OFFSET_MASK_IMAGE,
+                 totOffRow, totOffCol, inMask->numRows, inMask->numCols);
+        return false;
+    }
+
+    // Check for incorrect types
+    inType = inImage->type.type;
+    flatType = flatImage->type.type;
+    maskType = inMask->type.type;
+    if(PS_IS_PSELEMTYPE_COMPLEX(inType)) {
+        psError( PS_ERR_BAD_PARAMETER_TYPE, true,
+                 PS_ERRORTEXT_pmFlatField_TYPE_INPUT_IMAGE,
+                 inType);
+        return false;
+    } else if(PS_IS_PSELEMTYPE_COMPLEX(flatType)) {
+        psError( PS_ERR_BAD_PARAMETER_TYPE, true,
+                 PS_ERRORTEXT_pmFlatField_TYPE_FLAT_IMAGE,
+                 flatType);
+        return false;
+    } else if(inMask && inMask->type.type != PS_TYPE_MASK) {
+        psError( PS_ERR_BAD_PARAMETER_TYPE, true,
+                 PS_ERRORTEXT_pmFlatField_TYPE_MASK_IMAGE,
+                 maskType);
+        return false;
+    } else if(inType != flatType) {
+        psError( PS_ERR_BAD_PARAMETER_TYPE, true,
+                 PS_ERRORTEXT_pmFlatField_TYPE_MISMATCH,
+                 inType, flatType);
+        return false;
+    }
+
+    // Macro for all PS types
+    #define PM_FLAT_DIVISION(TYPE)                                                                           \
+case PS_TYPE_##TYPE:                                                                                         \
+    /* Per Eugene's request, use two sets of loops: first to fill mask, second to avoid div with bad pix */  \
+    for(j = totOffRow; j < inImage->numRows; j++) {                                                          \
+        for(i = totOffCol; i < inImage->numCols; i++) {                                                      \
+            if(flatImage->data.TYPE[j][i] <= 0.0) {                                                          \
+                /* Negative or zero flat pixels shall be masked in input image as  PM_MASK_FLAT */           \
+                if (inMask) {                                                                                \
+                    inMask->data.PS_TYPE_MASK_DATA[j][i] |= PM_MASK_FLAT;                                    \
+                }                                                                                            \
+                flatImage->data.TYPE[j][i] = 0.0;                                                            \
+            }                                                                                                \
+        }                                                                                                    \
+    }                                                                                                        \
+    for(j = totOffRow; j < inImage->numRows; j++) {                                                          \
+        for(i = totOffCol; i < inImage->numCols; i++) {                                                      \
+            if(inMask && !inMask->data.PS_TYPE_MASK_DATA[j][i]) {                                            \
+                /* Module shall divide the input image by the flat-fielded image */                          \
+                inImage->data.TYPE[j][i] /= flatImage->data.TYPE[j][i];                                      \
+            }                                                                                                \
+        }                                                                                                    \
+    }                                                                                                        \
+    break;
+
+    switch(inType) {
+        PM_FLAT_DIVISION(U8);
+        PM_FLAT_DIVISION(U16);
+        PM_FLAT_DIVISION(U32);
+        PM_FLAT_DIVISION(U64);
+        PM_FLAT_DIVISION(S8);
+        PM_FLAT_DIVISION(S16);
+        PM_FLAT_DIVISION(S32);
+        PM_FLAT_DIVISION(S64);
+        PM_FLAT_DIVISION(F32);
+        PM_FLAT_DIVISION(F64);
+    default:
+        psError( PS_ERR_BAD_PARAMETER_TYPE, true,
+                 PS_ERRORTEXT_pmFlatField_TYPE_UNSUPPORTED,
+                 inType);
+    }
+
+    return true;
+}
Index: /tags/rel11_ifa_pre0/psModules/src/detrend/pmFlatField.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/detrend/pmFlatField.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/detrend/pmFlatField.h	(revision 6961)
@@ -0,0 +1,47 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+
+/** @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.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:01:05 $
+ *
+ *  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/rel11_ifa_pre0/psModules/src/detrend/pmFlatFieldErrors.dat
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/detrend/pmFlatFieldErrors.dat	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/detrend/pmFlatFieldErrors.dat	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/detrend/pmFlatFieldErrors.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/detrend/pmFlatFieldErrors.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/detrend/pmFlatFieldErrors.h	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/detrend/pmFringeStats.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/detrend/pmFringeStats.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/detrend/pmFringeStats.c	(revision 6961)
@@ -0,0 +1,484 @@
+/** @file  pmFringeStats.c
+ *
+ *  @author Eugene Magnier, IfA
+ *
+ *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-22 03:21:00 $
+ *
+ *  Copyright 2004 IfA
+ */
+
+#include <stdio.h>
+#include <assert.h>
+#include "pslib.h"
+#include "pmFPA.h"
+#include "pmFringeStats.h"
+
+#define MAX(x,y) ((x) > (y) ? (x) : (y))
+#define MIN(x,y) ((x) < (y) ? (x) : (y))
+
+
+// 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)
+{
+
+    double frnd;
+    // create fringe->nRequested
+
+    psRandom *rnd = 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(rnd);
+        xPt[i] = (nX - 2*dX)* frnd + dX;
+        frnd = psRandomUniform(rnd);
+        yPt[i] = (nY - 2*dY)* frnd + dY;
+    }
+    return true;
+}
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// pmFringeStats
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+static void fringeStatsFree(pmFringeStats *stats)
+{
+    psFree(stats->regions);
+    psFree(stats->f);
+    psFree(stats->df);
+}
+
+pmFringeStats *pmFringeStatsAlloc(pmFringeRegions *regions)
+{
+    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)
+{
+    if (!fringe->x || !fringe->y) {
+        // create the fringe vectors for this image
+        pmFringeRegionsCreatePoints(fringe, readout->image);
+    }
+
+    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 over the image
+    psImage *sky = psImageAlloc(fringe->nX, fringe->nY, PS_TYPE_F32);
+    for (int i = 0; i < fringe->nY; i++) {
+        int y0 = (float)i * (float)image->numRows / (float)fringe->nY;
+        int y1 = (float)(i + 1) * (float)image->numRows / (float)fringe->nY;
+        for (int j = 0; j < fringe->nX; j++) {
+            int x0 = (float)j * (float)image->numCols / (float)fringe->nX;
+            int x1 = (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 = psImageSubset(mask, region); // Subimage of the sky region
+            psImageStats(median, subImage, subMask, maskVal);
+            sky->data.F32[i][j] = median->sampleMedian;
+        }
+    }
+
+    for (int i = 0; i < fringe->x->n; i++) {
+        psRegion region = psRegionSet(xPt[i] - dX, xPt[i] + dX + 1, yPt[i] - dY, yPt[i] + dY + 1);
+        region = psRegionForImage(image, region);
+        psImage *subImage = psImageSubset(image, region);
+        psImage *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(__func__, 7, "[%d:%d,%d:%d]: %f %f\n", (int)region.x0, (int)region.x1,
+                (int)region.y0, (int)region.y1, fPt[i], dfPt[i]);
+    }
+
+    return measurements;
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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 (! psGaussJordan(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(__func__, 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(__func__, 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)
+{
+    pmFringeRegions *regions = science->regions; // The fringe regions
+    int numRegions = regions->nRequested; // Number of regions
+    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(__func__, 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(__func__, 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(__func__, 1, "Fringe scales after iteration %d:\n", iter);
+        psTrace(__func__, 1, "Background: %f %f\n", scale->coeff->data.F32[0], scale->coeffErr->data.F32[0]);
+        for (int i = 0; i < scale->nFringeFrames; i++) {
+            psTrace(__func__, 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(__func__, 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)
+{
+    // 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(__func__, 7, "Fringe solution:\n");
+    for (int i = 0; i < fringeImages->n + 1; i++) {
+        psTrace(__func__, 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/rel11_ifa_pre0/psModules/src/detrend/pmFringeStats.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/detrend/pmFringeStats.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/detrend/pmFringeStats.h	(revision 6961)
@@ -0,0 +1,141 @@
+/** @file  pmFringeStats.h
+ *
+ *  @brief Measure Fringe statistics and apply correction
+ *
+ *  @author Eugene Magnier, IfA
+ *
+ *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-22 03:21:00 $
+ *
+ *  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)
+                                );
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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
+);
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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/rel11_ifa_pre0/psModules/src/detrend/pmMaskBadPixels.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/detrend/pmMaskBadPixels.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/detrend/pmMaskBadPixels.c	(revision 6961)
@@ -0,0 +1,188 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/** @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.
+ *
+ *  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.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-19 20:37:35 $
+ *
+ *  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 "pmMaskBadPixels.h"
+#include "pmMaskBadPixelsErrors.h"
+
+bool pmMaskBadPixels(pmReadout *in, const psImage *mask, unsigned int maskVal, float sat,
+                     unsigned int growVal, int grow)
+{
+    int i = 0;
+    int j = 0;
+    int jj = 0;
+    int ii = 0;
+    int rRound = 0;
+    int rowMin = 0;
+    int rowMax = 0;
+    int colMin = 0;
+    int colMax = 0;
+    int totOffCol = 0;
+    int totOffRow = 0;
+    float r = 0.0f;
+    psElemType inType;
+    psElemType maskType;
+    psImage *inImage = NULL;
+    psImage *inMask = NULL;
+
+
+    // Check for nulls
+    if (in == NULL) {
+        return true;   // Readout may not have data in it
+    } else if(mask==NULL) {
+        psError( PS_ERR_BAD_PARAMETER_NULL, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_NULL_MASK_IMAGE);
+        return false;
+    }
+
+    inImage = in->image;
+    if (inImage == NULL) {
+        psError( PS_ERR_BAD_PARAMETER_NULL, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_NULL_INPUT_IMAGE);
+        return false;
+    } else if(in->mask == NULL) {
+        in->mask = psImageAlloc(inImage->numCols, inImage->numRows, PS_TYPE_MASK);
+        memset(in->mask->data.V[0], 0, inImage->numCols*inImage->numRows*PSELEMTYPE_SIZEOF(PS_TYPE_MASK));
+    }
+    inMask = in->mask;
+
+    // Check input image and its mask are not larger than mask
+    if(inImage->numRows > mask->numRows || inImage->numCols > mask->numCols) {
+        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_SIZE_INPUT_IMAGE,
+                 inImage->numRows, inImage->numCols, mask->numRows, mask->numCols);
+        return false;
+    } else if(inMask->numRows>mask->numRows || inMask->numCols>mask->numCols) {
+        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_SIZE_MASK_IMAGE,
+                 inMask->numRows, inMask->numCols, mask->numRows, mask->numCols);
+        return false;
+    }
+
+    // Determine total offset based on image offset with chip offset
+    totOffCol = inImage->col0; // + in->col0;
+    totOffRow = inImage->row0; // + in->row0;
+
+    // Check that offsets are within image limits
+    if(totOffRow>=mask->numRows || totOffCol>=mask->numCols) {
+        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_OFFSET_MASK_IMAGE,
+                 totOffRow, totOffCol, mask->numRows, mask->numCols);
+        return false;
+    } else if(totOffRow>=inImage->numRows || totOffCol>=inImage->numCols) {
+        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_OFFSET_INPUT_IMAGE,
+                 totOffRow, totOffCol, inImage->numRows, inImage->numCols);
+        return false;
+    } else if(totOffRow>=inMask->numRows || totOffCol>=inMask->numCols) {
+        psError( PS_ERR_BAD_PARAMETER_SIZE, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_OFFSET_INPUT_IMAGE_MASK,
+                 totOffRow, totOffCol, inMask->numRows, inMask->numCols);
+        return false;
+    }
+
+    // Check for incorrect types
+    inType = inImage->type.type;
+    maskType = mask->type.type;
+    if(PS_IS_PSELEMTYPE_COMPLEX(inType)) {
+        psError( PS_ERR_BAD_PARAMETER_TYPE, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_TYPE_INPUT_IMAGE,
+                 inType);
+        return false;
+    } else if(maskType!=PS_TYPE_MASK) {
+        psError( PS_ERR_BAD_PARAMETER_TYPE, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_TYPE_MASK_IMAGE,
+                 maskType);
+        return false;
+    }
+
+    // Macro for all PS types
+    #define PM_BAD_PIXELS(TYPE)                                                                              \
+case PS_TYPE_##TYPE:                                                                                         \
+    for(j=totOffRow; j<inImage->numRows; j++) {                                                              \
+        for(i=totOffCol; i<inImage->numCols; i++) {                                                          \
+            \
+            /* Pixels with flux greater than sat shall be masked */                                          \
+            if(inImage->data.TYPE[j][i] > sat) {                                                             \
+                inMask->data.PS_TYPE_MASK_DATA[j][i] |= PM_MASK_SAT;                                         \
+            }                                                                                                \
+            \
+            /* Pixels which satisfy maskVal shall be masked */                                               \
+            inMask->data.PS_TYPE_MASK_DATA[j][i] |= (mask->data.PS_TYPE_MASK_DATA[j][i]&maskVal);            \
+            \
+            /* Pixels which satisfy growVal and within the grow radius shall be masked */                    \
+            if(mask->data.PS_TYPE_MASK_DATA[j][i] & growVal) {                                               \
+                rowMin = PS_MAX(j-grow, 0);                                                                     \
+                rowMax = PS_MIN(j+grow+1, inImage->numRows);                                                    \
+                colMin = PS_MAX(i-grow, 0);                                                                     \
+                colMax = PS_MIN(i+grow+1, inImage->numCols);                                                    \
+                for(jj=rowMin; jj<rowMax; jj++) {                                                            \
+                    for(ii=colMin; ii<colMax; ii++) {                                                        \
+                        r = sqrtf((ii-i)*(ii-i)+(jj-j)*(jj-j));                                              \
+                        rRound = r + 0.5;                                                                    \
+                        if(rRound <= grow) {                                                                 \
+                            inMask->data.PS_TYPE_MASK_DATA[jj][ii] |=                                        \
+                                    (mask->data.PS_TYPE_MASK_DATA[j][i]&growVal);                            \
+                        }                                                                                    \
+                    }                                                                                        \
+                }                                                                                            \
+            }                                                                                                \
+        }                                                                                                    \
+    }                                                                                                        \
+    break;
+
+    // Switch to call bad pixel masking macro defined above
+    switch(inType) {
+        PM_BAD_PIXELS(U8);
+        PM_BAD_PIXELS(U16);
+        PM_BAD_PIXELS(U32);
+        PM_BAD_PIXELS(U64);
+        PM_BAD_PIXELS(S8);
+        PM_BAD_PIXELS(S16);
+        PM_BAD_PIXELS(S32);
+        PM_BAD_PIXELS(S64);
+        PM_BAD_PIXELS(F32);
+        PM_BAD_PIXELS(F64);
+    default:
+        psError( PS_ERR_BAD_PARAMETER_TYPE, true,
+                 PS_ERRORTEXT_pmMaskBadPixels_TYPE_UNSUPPORTED,
+                 inType);
+    }
+
+    return false;
+}
Index: /tags/rel11_ifa_pre0/psModules/src/detrend/pmMaskBadPixels.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/detrend/pmMaskBadPixels.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/detrend/pmMaskBadPixels.h	(revision 6961)
@@ -0,0 +1,75 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// XXX WARNING: I have completely replaced this file with an OLD VERSION (that works) instead of the
+// one that was being worked on.
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+/** @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.4 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-19 20:37:35 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ */
+
+#include "pslib.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 a charge trap.
+    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;
+
+bool pmReadoutSetMask(pmReadout *readout // Readout for which to set mask
+                     );
+bool pmReadoutSetWeight(pmReadout *readout // Readout for which to set weight
+                       );
+bool pmCellSetMaskWeight(pmCell *cell // Cell for which to set weights
+                        );
+
+/** Execute bad pixels module.
+ *
+ *  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.
+ *
+ *  @return  bool: True or false for success or failure
+ */
+bool pmMaskBadPixels(
+    pmReadout *in,          ///< Readout containing input image data.
+    const psImage *mask,    ///< Mask data to be added to readout mask data.
+    unsigned int maskVal,   ///< Mask value to determine what to add to input mask.
+    float sat,              ///< Saturation limit to mask bad pixels.
+    unsigned int growVal,   ///< Mask data to determine if a circurlar area should be masked.
+    int grow                ///< Radius of mask to apply around pixel.
+);
+
Index: /tags/rel11_ifa_pre0/psModules/src/detrend/pmMaskBadPixelsErrors.dat
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/detrend/pmMaskBadPixelsErrors.dat	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/detrend/pmMaskBadPixelsErrors.dat	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/detrend/pmMaskBadPixelsErrors.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/detrend/pmMaskBadPixelsErrors.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/detrend/pmMaskBadPixelsErrors.h	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/detrend/pmNonLinear.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/detrend/pmNonLinear.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/detrend/pmNonLinear.c	(revision 6961)
@@ -0,0 +1,128 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.7 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:10:08 $
+ *
+ *  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 "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 (%d, %d)\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/rel11_ifa_pre0/psModules/src/detrend/pmNonLinear.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/detrend/pmNonLinear.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/detrend/pmNonLinear.h	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/imcombine/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/imcombine/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/imcombine/.cvsignore	(revision 6961)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/rel11_ifa_pre0/psModules/src/imcombine/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/imcombine/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/imcombine/Makefile.am	(revision 6961)
@@ -0,0 +1,11 @@
+noinst_LTLIBRARIES = libpsmoduleimcombine.la
+
+libpsmoduleimcombine_la_CPPFLAGS = $(SRCINC) $(PSMODULE_CFLAGS)
+libpsmoduleimcombine_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmoduleimcombine_la_SOURCES  = pmImageCombine.c
+#    pmReadoutCombine.c
+
+psmoduleincludedir = $(includedir)
+psmoduleinclude_HEADERS = \
+    pmImageCombine.h
+#    pmReadoutCombine.h
Index: /tags/rel11_ifa_pre0/psModules/src/imcombine/pmImageCombine.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/imcombine/pmImageCombine.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/imcombine/pmImageCombine.c	(revision 6961)
@@ -0,0 +1,675 @@
+/** @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.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-03-04 01:01:33 $
+ *
+ *  XXX: pmRejectPixels() has a known bug with the pmImageTransform() call.
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#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
+    const psStats *stats)               ///< Statistics to use in the combination
+{
+
+    PS_ASSERT_PTR_NON_NULL(images, combine);
+    psU32 numImages = images->n;
+    psTrace("ImageCombine.pmCombineImages", 3, "Calling pmCombineImages(%d)\n", images->n);
+
+    if (errors != NULL) {
+        if (images->n != errors->n) {
+            psError(PS_ERR_UNKNOWN, true, "images and errors args must have same length (%d != %d)\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 (%d != %d)\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);
+
+    // Allocate and initialize the combined image, if necessary.
+    if (combine == NULL) {
+        combine = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+        if (pixels != NULL) {
+            PS_IMAGE_SET_F32(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);
+    PS_VECTOR_SET_S32(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);
+        PS_VECTOR_SET_U8(pixelMask, 0);
+        pixelMask->n = numImages;
+    }
+
+    psVector *pixelErrors = NULL;
+    if (errors != NULL) {
+        pixelErrors = psVectorAlloc(numImages, PS_TYPE_F32);
+        PS_VECTOR_SET_F32(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.
+            PS_VECTOR_SET_U8(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((psStats *) stats, pixelData, pixelErrors, pixelMask, maskVal);
+                psF64 combinedPixel;
+                psBool rc = p_psGetStatValue(stats, &combinedPixel);
+                if (rc != true) {
+                    psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not combine pixels (%d, %d) with the specified stat.\n", row, col);
+                }
+                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;
+                    psBool rc = p_psGetStatValue(stdevStats, &stdev);
+                    if (rc != true) {
+                        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not compute the standard deviation of pixel (%d, %d).\n", row, col);
+                        psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not combine pixels (%d, %d) with the specified stat.\n", row, col);
+                    }
+
+                    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((psStats *) stats, pixelData, pixelErrors, pixelMask, maskVal);
+                psF64 tmpF64;
+                psBool rc = p_psGetStatValue(stats, &tmpF64);
+                combine->data.F32[row][col] = (psF32) tmpF64;
+                if (rc != true) {
+                    psLogMsg(__func__, PS_LOG_WARN, "WARNING: could not combine pixels (%d, %d) with the specified stat.\n", row, col);
+                }
+            }
+        }
+    }
+
+    psFree(pixelData);
+    psFree(pixelMask);
+    psFree(pixelErrors);
+    psFree(qpPtr);
+
+    psTrace("ImageCombine.pmCombineImages", 3, "Exiting pmCombineImages(%d)\n", images->n);
+    return(combine);
+}
+
+
+/******************************************************************************
+XXX: Directly from Paul Price
+ *****************************************************************************/
+static psF32 CalcGradient(
+    psImage *image,
+    psImage *imageMask,
+    psS32 x,
+    psS32 y
+)
+{
+    psTrace("ImageCombine.CalcGradient", 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("ImageCombine.CalcGradient", 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("ImageCombine.DetermineRegion", 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("ImageCombine.DetermineRegion", 4, "Exiting DetermineRegion()\n");
+    return(myRegion);
+}
+
+/******************************************************************************
+XXX: Don't we have a psLib function for this?
+ *****************************************************************************/
+static psImage *ImageConvertF32(psImage *image)
+{
+    psTrace("ImageCombine.ImageConvertF32", 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("ImageCombine.ImageConvertF32", 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("ImageCombine.pmRejectPixels", 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);
+    PS_VECTOR_SET_S32(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("ImageCombine.pmRejectPixels", 3, "Exiting pmRejectPixels()\n");
+    return(rejects);
+}
Index: /tags/rel11_ifa_pre0/psModules/src/imcombine/pmImageCombine.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/imcombine/pmImageCombine.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/imcombine/pmImageCombine.h	(revision 6961)
@@ -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.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2005-09-28 20:43:52 $
+ *
+ *  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
+    const 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/rel11_ifa_pre0/psModules/src/imcombine/pmReadoutCombine.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/imcombine/pmReadoutCombine.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/imcombine/pmReadoutCombine.c	(revision 6961)
@@ -0,0 +1,857 @@
+/** @file  pmReadoutCombine.c
+ *
+ *  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-04-17 18:10:08 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#include<stdio.h>
+#include<math.h>
+#include "pslib.h"
+#include "pmReadoutCombine.h"
+
+/******************************************************************************
+DetermineNumBits(data): This routine takes an enum psStatsOptions as an
+argument and returns the number of non-zero bits.
+ *****************************************************************************/
+static psS32 DetermineNumBits(psStatsOptions data)
+{
+    psS32 i;
+    psU64 tmpData = data;
+    psS32 numBits = 0;
+
+    for (i=0;i<(8 * sizeof(psStatsOptions));i++) {
+        if (0x0001 & tmpData) {
+            numBits++;
+        }
+        tmpData = tmpData >> 1;
+    }
+    return(numBits);
+}
+
+static void pmCombineParamsFree (pmCombineParams *params)
+{
+
+    if (params == NULL)
+        return;
+
+    psFree (params->stats);
+    return;
+}
+
+pmCombineParams *pmCombineParamsAlloc (psStatsOptions statsOptions)
+{
+
+    pmCombineParams *params = psAlloc (sizeof(pmCombineParams));
+    psMemSetDeallocator(params, (psFreeFunc) pmCombineParamsFree);
+
+    params->stats = psStatsAlloc (statsOptions);
+    params->maskVal = 0;
+    params->fracHigh = 0.25;
+    params->fracHigh = 0.25;
+    params->nKeep = 3;
+
+    return (params);
+}
+
+/******************************************************************************
+XXX: Must add support for S16 and S32 types.  F32 currently supported.
+ *****************************************************************************/
+psImage *pmReadoutCombine(psImage *output,
+                          const psArray *inputs,
+                          const psVector *zero,
+                          const psVector *scale,
+                          pmCombineParams *params,
+                          bool applyZeroScale,
+                          psF32 gain,
+                          psF32 readnoise)
+{
+    PS_ASSERT_PTR_NON_NULL(inputs, NULL);
+    PS_ASSERT_PTR_NON_NULL(params, NULL);
+    PS_ASSERT_PTR_NON_NULL(params->stats, NULL);
+    if (zero != NULL) {
+        PS_ASSERT_VECTOR_TYPE(zero, PS_TYPE_F32, NULL);
+        //        PS_ASSERT_VECTOR_TYPE_S16_S32_F32(zero, NULL);
+    }
+    if (scale != NULL) {
+        PS_ASSERT_VECTOR_TYPE(scale, PS_TYPE_F32, NULL);
+        //        PS_ASSERT_VECTOR_TYPE_S16_S32_F32(scale, NULL);
+    }
+    if ((zero != NULL) && (scale != NULL)) {
+        PS_ASSERT_VECTOR_TYPE_EQUAL(zero, scale, NULL);
+        // PS_ASSERT_VECTOR_TYPE_S16_S32_F32(scale, NULL);
+    }
+
+    psStats *stats = params->stats;
+    psS32 maxInputCols = 0;
+    psS32 maxInputRows = 0;
+    psS32 minInputCols = PS_MAX_S32;
+    psS32 minInputRows = PS_MAX_S32;
+    pmReadout *tmpReadout = NULL;
+    psS32 tmpI;
+    psElemType outputType = PS_TYPE_F32;
+
+    if (DetermineNumBits(stats->options) != 1) {
+        psError(PS_ERR_UNKNOWN, true,
+                "Multiple statistical options have been requested.  Returning NULL.\n");
+        return(NULL);
+    }
+
+    // We step through each readout in the input image list.  If any readout is
+    // NULL, empty, or has the wrong type, we generate an error and return
+    // NULL.  We determine how big of an output image is needed to combine
+    // these input images.  We do this by taking the
+    //     max(readout->col0 + readout->numCols + image->col0 + image->numCols)
+    //     max(readout->row0 + readout->numRows + image->row0 + image->numRows)
+    //
+    for (int i = 0; i < inputs->n; i++) {
+        tmpReadout = inputs->data[i];
+        PS_ASSERT_READOUT_NON_NULL(tmpReadout, output);
+        PS_ASSERT_READOUT_NON_EMPTY(tmpReadout, output);
+        PS_ASSERT_READOUT_TYPE(tmpReadout, PS_TYPE_F32, output);
+
+        minInputRows = PS_MIN(minInputRows, (tmpReadout->row0 + tmpReadout->image->row0));
+        tmpI = tmpReadout->row0 +
+               tmpReadout->image->row0 +
+               tmpReadout->image->numRows;
+        maxInputRows = PS_MAX(maxInputRows, tmpI);
+
+        minInputCols = PS_MIN(minInputCols, (tmpReadout->col0 + tmpReadout->image->col0));
+        tmpI = tmpReadout->col0 +
+               tmpReadout->image->col0 +
+               tmpReadout->image->numCols;
+        maxInputCols = PS_MAX(maxInputCols, tmpI);
+    }
+
+    // We ensure that the zero vector is of the proper size.
+    if (zero != NULL) {
+        PS_ASSERT_VECTOR_TYPE(zero, PS_TYPE_F32, NULL);
+        if (zero->n < inputs->n) {
+            psError(PS_ERR_UNKNOWN, true, "zero vector has incorrect size (%d).  Returning NULL.\n", zero->n);
+            return(NULL);
+        } else if (zero->n > inputs->n) {
+            // XXX EAM : abort on this condition? is probably an error
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "WARNING: the zero vector too many elements (%d)\n", zero->n);
+        }
+    }
+
+    // We ensure that the scale vector is of the proper size.
+    if (scale != NULL) {
+        PS_ASSERT_VECTOR_TYPE(scale, PS_TYPE_F32, NULL);
+        if (scale->n < inputs->n) {
+            psError(PS_ERR_UNKNOWN, true, "scale vector has incorrect size (%d).  Returning NULL.\n", scale->n);
+            return(NULL);
+        } else if (scale->n > inputs->n) {
+            // XXX EAM : abort on this condition? is probably an error
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "WARNING: the scale vector has too many elements (%d)\n", scale->n);
+        }
+    }
+
+    // At this point, the following variables have been computed:
+    // maxInputRows: the largest input row value, in output image space.
+    // maxInputCols: the largest input column value, in output image space.
+    // minInputRows: the smallest input row value, in output image space.
+    // minInputCols: the smallest input column value, in output image space.
+    //
+    if (output == NULL) {
+        output = psImageAlloc(maxInputCols-minInputCols, maxInputRows-minInputRows, outputType);
+        *(psS32 *) &(output->col0) = minInputCols;
+        *(psS32 *) &(output->row0) = minInputRows;
+    } else {
+
+        // XXX EAM : recycle the existing output image?  why would we care about the existing pixels?
+        PS_ASSERT_IMAGE_TYPE(output, PS_TYPE_F32, NULL);
+        if (((output->col0 + output->numCols) < maxInputCols) ||
+                ((output->row0 + output->numRows) < maxInputRows)) {
+            psError(PS_ERR_UNKNOWN, true,
+                    "Output image (%d, %d) is too small to hold combined images.  Returning NULL.\n",
+                    output->row0 + output->numRows,
+                    output->col0 + output->numCols);
+            return(NULL);
+        }
+
+        // reset output origin using logic of above
+        *(psS32 *) &(output->col0) = minInputCols;
+        *(psS32 *) &(output->row0) = minInputRows;
+    }
+
+    psVector *tmpPixels     = psVectorAlloc(inputs->n, PS_TYPE_F32);
+    psVector *tmpPixelsKeep = psVectorAlloc(inputs->n, PS_TYPE_F32);
+    psVector *outRowLower   = psVectorAlloc(inputs->n, PS_TYPE_U32);
+    psVector *outRowUpper   = psVectorAlloc(inputs->n, PS_TYPE_U32);
+    psVector *outColLower   = psVectorAlloc(inputs->n, PS_TYPE_U32);
+    psVector *outColUpper   = psVectorAlloc(inputs->n, PS_TYPE_U32);
+
+    // For each input readout, we store the min/max pixel indices for that readout, in detector coordinates,
+    // in the psVectors (outRowLower, outColLower, outRowUpper, outColUpper).
+    for (int i = 0; i < inputs->n; i++) {
+        tmpReadout = (pmReadout *) inputs->data[i];
+        outRowLower->data.U32[i] = tmpReadout->row0 + tmpReadout->image->row0;
+        outColLower->data.U32[i] = tmpReadout->col0 + tmpReadout->image->col0;
+        outRowUpper->data.U32[i] = tmpReadout->row0 +
+                                   tmpReadout->image->row0 +
+                                   tmpReadout->image->numRows;
+        outColUpper->data.U32[i] = tmpReadout->col0 +
+                                   tmpReadout->image->col0 +
+                                   tmpReadout->image->numCols;
+    }
+
+    // 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 tmpPixels.
+    // If not, we set a mask for that element in tmpPixels.  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.
+
+    int nx, ny;
+    int nKeep, nMin;
+    float keepFrac = 1.0 - params->fracLow - params->fracHigh;
+    float value = 0;
+    psF32 *saveVector = tmpPixelsKeep->data.F32;
+
+    for (int j = output->row0; j < (output->row0 + output->numRows) ; j++) {
+        if (j % 10 == 0)
+            fprintf (stderr, ".");
+        for (int i = output->col0; i < (output->col0 + output->numCols) ; i++) {
+            int nPix = 0;
+            for (int r = 0; r < inputs->n; r++) {
+                tmpReadout = (pmReadout *) inputs->data[r];
+
+                // psTrace (__func__, 6, "[%d][%d]: [%d][%d] to [%d][%d]\n", i, j, outColLower->data.U32[r], outRowLower->data.U32[r], outColUpper->data.U32[r], outRowUpper->data.U32[r]);
+                if (i <  outColLower->data.U32[r])
+                    continue;
+                if (i >= outColUpper->data.U32[r])
+                    continue;
+                if (j <  outRowLower->data.U32[r])
+                    continue;
+                if (j >= outRowUpper->data.U32[r])
+                    continue;
+
+                nx = i - (tmpReadout->col0 + tmpReadout->image->col0);
+                ny = j - (tmpReadout->row0 + tmpReadout->image->row0);
+
+                if (tmpReadout->mask != NULL) {
+                    if (tmpReadout->mask->data.U8[ny][nx] && params->maskVal)
+                        continue;
+                }
+
+                tmpPixels->data.F32[nPix] = tmpReadout->image->data.F32[ny][nx];
+                // psTrace (__func__, 6, "readout[%d], image [%d][%d] is %f\n", r, i, j, tmpPixels->data.F32[nPix]);
+                nPix ++;
+            }
+            tmpPixels->n = nPix;
+
+            // are there enough valid pixels to apply fracLow,fracHigh?
+            nKeep = nPix * keepFrac;
+            if (nKeep >= params->nKeep) {
+                psVectorSort (tmpPixels, tmpPixels);
+                nMin = nPix * params->fracLow;
+                tmpPixelsKeep->data.F32 = &tmpPixels->data.F32[nMin];
+                tmpPixelsKeep->n = nKeep;
+            } else {
+                tmpPixelsKeep->data.F32 = tmpPixels->data.F32;
+                tmpPixelsKeep->n = nPix;
+            }
+
+            // tmpPixelsKeep is already sorted.  sample mean and median are very easy
+            if (stats->options & PS_STAT_SAMPLE_MEAN) {
+                value = 0;
+                for (int r = 0; r < tmpPixelsKeep->n; r++) {
+                    value += tmpPixelsKeep->data.F32[r];
+                }
+                if (tmpPixelsKeep->n == 0) {
+                    value = 0;
+                } else {
+                    value = value / tmpPixelsKeep->n;
+                }
+            }
+            if (stats->options & PS_STAT_SAMPLE_MEDIAN) {
+                int r = tmpPixelsKeep->n / 2;
+                if (tmpPixelsKeep->n == 0) {
+                    value = 0;
+                    goto got_value;
+                }
+                if (tmpPixelsKeep->n % 2 == 1) {
+                    int r = 0.5*tmpPixelsKeep->n;
+                    value = tmpPixelsKeep->data.F32[r];
+                    goto got_value;
+                }
+                if (tmpPixelsKeep->n % 2 == 0) {
+                    value = 0.5*(tmpPixelsKeep->data.F32[r] +
+                                 tmpPixelsKeep->data.F32[r-1]);
+                    goto got_value;
+                }
+            }
+got_value:
+            output->data.F32[j-output->row0][i-output->col0] = value;
+        }
+    }
+    tmpPixelsKeep->data.F32 = saveVector;
+
+    psFree(tmpPixels);
+    psFree(tmpPixelsKeep);
+    psFree(outRowLower);
+    psFree(outRowUpper);
+    psFree(outColLower);
+    psFree(outColUpper);
+
+    return(output);
+}
+
+/******************************************************************************
+XXX: Must add support for S16 and S32 types.  F32 currently supported.
+ *****************************************************************************/
+psImage *pmReadoutCombine_OLD(psImage *output,
+                              const psList *inputs,
+                              pmCombineParams *params,
+                              const psVector *zero,
+                              const psVector *scale,
+                              bool applyZeroScale,
+                              psF32 gain,
+                              psF32 readnoise)
+{
+    PS_ASSERT_PTR_NON_NULL(inputs, NULL);
+    PS_ASSERT_PTR_NON_NULL(params, NULL);
+    PS_ASSERT_PTR_NON_NULL(params->stats, NULL);
+    if (zero != NULL) {
+        PS_ASSERT_VECTOR_TYPE(zero, PS_TYPE_F32, NULL);
+        //        PS_ASSERT_VECTOR_TYPE_S16_S32_F32(zero, NULL);
+    }
+    if (scale != NULL) {
+        PS_ASSERT_VECTOR_TYPE(scale, PS_TYPE_F32, NULL);
+        //        PS_ASSERT_VECTOR_TYPE_S16_S32_F32(scale, NULL);
+    }
+    if ((zero != NULL) && (scale != NULL)) {
+        PS_ASSERT_VECTOR_TYPE_EQUAL(zero, scale, NULL);
+        // PS_ASSERT_VECTOR_TYPE_S16_S32_F32(scale, NULL);
+    }
+
+    psStats *stats = params->stats;
+    psS32 i;
+    psS32 j;
+    psS32 maxInputCols = 0;
+    psS32 maxInputRows = 0;
+    psS32 minInputCols = PS_MAX_S32;
+    psS32 minInputRows = PS_MAX_S32;
+    psListElem *tmpInput = NULL;
+    pmReadout *tmpReadout = NULL;
+    psS32 numInputs = 0;
+    psS32 tmpI;
+    psElemType outputType = PS_TYPE_F32;
+
+    if (1 < DetermineNumBits(params->stats->options)) {
+        psError(PS_ERR_UNKNOWN, true,
+                "Multiple statistical options have been requested.  Returning NULL.\n");
+        return(NULL);
+    }
+
+    //
+    // We step through each readout in the input image list.  If any readout is
+    // NULL, empty, or has the wrong type, we generate an error and return
+    // NULL.  We determine how big of an output image is needed to combine
+    // these input images.  We do this by taking the
+    //     max(readout->col0 + readout->numCols + image->col0 + image->numCols)
+    //     max(readout->row0 + readout->numRows + image->row0 + image->numRows)
+    // We then compare that to
+    //     output->col0 + output->numCols
+    //     output->row0 + output->numRows
+    // to determine if the output image actually stores that pixel.  A similar
+    // thing is done for the minimum row and column.
+    //
+    tmpInput = (psListElem *) inputs->head;
+    while (NULL != tmpInput) {
+        tmpReadout = (pmReadout *) tmpInput->data;
+        PS_ASSERT_READOUT_NON_NULL(tmpReadout, output);
+        PS_ASSERT_READOUT_NON_EMPTY(tmpReadout, output);
+        PS_ASSERT_READOUT_TYPE(tmpReadout, PS_TYPE_F32, output);
+
+        outputType = tmpReadout->image->type.type;
+
+        minInputRows = PS_MIN(minInputRows,
+                              (tmpReadout->row0 + tmpReadout->image->row0));
+        tmpI = tmpReadout->row0 +
+               tmpReadout->image->row0 +
+               tmpReadout->image->numRows;
+        maxInputRows = PS_MAX(maxInputRows, tmpI);
+
+        minInputCols = PS_MIN(minInputCols,
+                              (tmpReadout->col0 + tmpReadout->image->col0));
+        tmpI = tmpReadout->col0 +
+               tmpReadout->image->col0 +
+               tmpReadout->image->numCols;
+        maxInputCols = PS_MAX(maxInputCols, tmpI);
+        tmpInput = tmpInput->next;
+        numInputs++;
+    }
+
+    // We ensure that the zero vector is of the proper size.
+    if (zero != NULL) {
+        PS_ASSERT_VECTOR_TYPE(zero, PS_TYPE_F32, NULL);
+        if (numInputs > zero->n) {
+            psError(PS_ERR_UNKNOWN, true, "zero vector has incorrect size (%d).  Returning NULL.\n", zero->n);
+            return(NULL);
+        } else if (numInputs < zero->n) {
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "WARNING: the zero vector too many elements (%d)\n", zero->n);
+        }
+    }
+
+    // We ensure that the scale vector is of the proper size.
+    if (scale != NULL) {
+        PS_ASSERT_VECTOR_TYPE(scale, PS_TYPE_F32, NULL);
+        if (numInputs > scale->n) {
+            psError(PS_ERR_UNKNOWN, true, "scale vector has incorrect size (%d).  Returning NULL.\n", scale->n);
+            return(NULL);
+        } else if (numInputs < scale->n) {
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "WARNING: the scale vector has too many elements (%d)\n", scale->n);
+        }
+    }
+
+    // At this point, the following variables have been computed:
+    // maxInputRows: the largest input row value, in output image space.
+    // maxInputCols: the largest input column value, in output image space.
+    // minInputRows: the smallest input row value, in output image space.
+    // minInputCols: the smallest input column value, in output image space.
+    //
+    if (output == NULL) {
+        output = psImageAlloc(maxInputCols-minInputCols,
+                              maxInputRows-minInputRows, outputType);
+        *(psS32 *) &(output->col0) = minInputCols;
+        *(psS32 *) &(output->row0) = minInputRows;
+    } else {
+        PS_ASSERT_IMAGE_TYPE(output, PS_TYPE_F32, NULL);
+        if (((output->col0 + output->numCols) < maxInputCols) ||
+                ((output->row0 + output->numRows) < maxInputRows)) {
+            psError(PS_ERR_UNKNOWN, true,
+                    "Output image (%d, %d) is too small to hold combined images.  Returning NULL.\n",
+                    output->row0 + output->numRows,
+                    output->col0 + output->numCols);
+            return(NULL);
+        }
+
+        if ((output->col0 > minInputCols) || (output->row0 > minInputRows)) {
+            psError(PS_ERR_UNKNOWN, true,
+                    "Output image offset is larger then input image offset.  Returning NULL.\n");
+            return(NULL);
+        }
+    }
+
+    psVector *tmpPixels = psVectorAlloc(numInputs, PS_TYPE_F32);
+    tmpPixels->n = tmpPixels->nalloc;
+    psVector *tmpPixelErrors = psVectorAlloc(numInputs, PS_TYPE_F32);
+    tmpPixelErrors->n = tmpPixelErrors->nalloc;
+    psVector *tmpPixelMask = psVectorAlloc(numInputs, PS_TYPE_U8);
+    tmpPixelMask->n = tmpPixelMask->nalloc;
+    psVector *tmpPixelMaskNKeep = psVectorAlloc(numInputs, PS_TYPE_U8);
+    tmpPixelMaskNKeep->n = tmpPixelMaskNKeep->nalloc;
+    psVector *outRowLower = psVectorAlloc(numInputs, PS_TYPE_U32);
+    outRowLower->n = outRowLower->nalloc;
+    psVector *outRowUpper = psVectorAlloc(numInputs, PS_TYPE_U32);
+    outRowUpper->n = outRowUpper->nalloc;
+    psVector *outColLower = psVectorAlloc(numInputs, PS_TYPE_U32);
+    outColLower->n = outColLower->nalloc;
+    psVector *outColUpper = psVectorAlloc(numInputs, PS_TYPE_U32);
+    outColUpper->n = outColUpper->nalloc;
+    pmReadout **tmpReadouts = (pmReadout **) psAlloc(numInputs * sizeof(pmReadout *));
+
+    // For each input readout, we create a pointer to that readout in
+    // "tmpReadouts[]", and we store the min/max pixel indices for that
+    // readout, in output image coordinates, in the psVectors
+    // (outRowLower, outColLower, outRowUpper, outColUpper).
+    i = 0;
+    tmpInput = (psListElem *) inputs->head;
+    while (NULL != tmpInput) {
+        tmpReadouts[i] = (pmReadout *) tmpInput->data;
+        outRowLower->data.U32[i] = tmpReadouts[i]->row0 + tmpReadouts[i]->image->row0;
+        outColLower->data.U32[i] = tmpReadouts[i]->col0 + tmpReadouts[i]->image->col0;
+        outRowUpper->data.U32[i] = tmpReadouts[i]->row0 +
+                                   tmpReadouts[i]->image->row0 +
+                                   tmpReadouts[i]->image->numRows;
+        outColUpper->data.U32[i] = tmpReadouts[i]->col0 +
+                                   tmpReadouts[i]->image->col0 +
+                                   tmpReadouts[i]->image->numCols;
+        tmpInput = tmpInput->next;
+        i++;
+    }
+
+    // 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 tmpPixels.
+    // If not, we set a mask for that element in tmpPixels.  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.
+
+    for (i = output->row0; i < (output->row0 + output->numRows) ; i++) {
+        for (j = output->col0; j < (output->col0 + output->numCols) ; j++) {
+            for (psS32 r = 0; r < numInputs ; r++) {
+                //  printf("[%d][%d]: [%d][%d] to [%d][%d]\n", i, j, outRowLower->data.U32[r], outColLower->data.U32[r], outRowUpper->data.U32[r], outColUpper->data.U32[r]);
+                if ((outRowLower->data.U32[r] <= i) &&
+                        (outColLower->data.U32[r] <= j) &&
+                        (outRowUpper->data.U32[r] > i) &&
+                        (outColUpper->data.U32[r] > j)) {
+
+                    psS32 imageRow = i - (tmpReadouts[r]->row0 +
+                                          tmpReadouts[r]->image->row0);
+                    psS32 imageCol = j - (tmpReadouts[r]->col0 +
+                                          tmpReadouts[r]->image->col0);
+
+                    if ((NULL == tmpReadouts[r]->mask) ||
+                            !(params->maskVal && tmpReadouts[r]->mask->data.U8[imageRow][imageCol])) {
+                        tmpPixels->data.F32[r] = tmpReadouts[r]->image->data.F32[imageRow][imageCol];
+                        tmpPixelMask->data.U8[r] = 0;
+                    } else {
+                        tmpPixels->data.F32[r] = 0.0;
+                        tmpPixelMask->data.U8[r] = 1;
+                    }
+                } else {
+                    tmpPixels->data.F32[r] = 0.0;
+                    tmpPixelMask->data.U8[r] = 1;
+                }
+                // printf("readout[%d], image [%d][%d] is %f\n", r, i, j, tmpPixels->data.F32[r]);
+            }
+            // At this point, we have scanned all input readouts for this
+            // one output pixel.
+            //            for (psS32 r = 0; r < numInputs ; r++) printf("(0)tmpPixels->data.F32[%d] is %f\n", r, tmpPixels->data.F32[r]);
+
+            // Determine how many pixels lie between fracLow and fracHigh.
+            psS32 pixelCount = 0;
+            for (psS32 r = 0; r < numInputs ; r++) {
+                if (tmpPixelMask->data.U8[r] == 0) {
+                    if ((params->fracLow <= tmpPixels->data.F32[r]) &&
+                            (params->fracHigh >= tmpPixels->data.F32[r])) {
+                        pixelCount++;
+                    }
+                }
+            }
+
+            // If more than params->nKeep pixels lie between the valid range,
+            // then loop through the pixels, and mask away any pixels outside
+            // that range.
+            if (pixelCount >= params->nKeep) {
+                for (psS32 r = 0; r < numInputs ; r++) {
+                    if (tmpPixelMask->data.U8[r] == 0) {
+                        if ((params->fracLow <= tmpPixels->data.F32[r]) &&
+                                (params->fracHigh >= tmpPixels->data.F32[r])) {
+                            tmpPixelMaskNKeep->data.U8[r] = 0;
+                        } else {
+                            tmpPixelMaskNKeep->data.U8[r] = 1;
+                        }
+                    }
+                }
+            }
+
+            if ((gain > 0.0) && (readnoise >= 0.0)) {
+                psF32 x;
+                psF32 sigma;
+                if (applyZeroScale == true) {
+                    for (psS32 r = 0; r < numInputs ; r++) {
+                        if (zero != NULL) {
+                            x = zero->data.F32[r];
+                        } else {
+                            x = 0.0;
+                        }
+                        if (scale != NULL) {
+                            x+= tmpPixels->data.F32[r] * scale->data.F32[r];
+                        } else {
+                            x+= tmpPixels->data.F32[r];
+                        }
+                        sigma = sqrtf((readnoise*readnoise) + gain * x) / gain;
+
+                        tmpPixelErrors->data.F32[r] = sigma;
+                        tmpPixels->data.F32[r]= x;
+                    }
+                } else {
+                    for (psS32 r = 0; r < numInputs ; r++) {
+                        x= tmpPixels->data.F32[r];
+
+                        if (zero != NULL) {
+                            sigma = zero->data.F32[r];
+                        } else {
+                            sigma = 0.0;
+                        }
+                        if (scale != NULL) {
+                            sigma+= tmpPixels->data.F32[r] * scale->data.F32[r];
+                        } else {
+                            sigma+= tmpPixels->data.F32[r];
+                        }
+                        sigma = sqrtf((readnoise*readnoise) + (gain * sigma)) / gain;
+
+                        tmpPixelErrors->data.F32[r] = sigma;
+                        tmpPixels->data.F32[r]= x;
+                    }
+                }
+                // Calculate the specified statistic on the stack of pixels.
+                //                for (psS32 r = 0; r < numInputs ; r++) printf("(1)tmpPixels->data.F32[%d] is %f\n", r, tmpPixels->data.F32[r]);
+                psStats *rc = psVectorStats(stats,
+                                            tmpPixels,
+                                            tmpPixelErrors,
+                                            tmpPixelMaskNKeep,
+                                            1);
+                if (rc == NULL) {
+                    psError(PS_ERR_UNKNOWN, false, "psVectorStats(): could not perform requested statistical operation.  Returning NULL.\n");
+                    return(NULL);
+                }
+            } else {
+                if (scale != NULL) {
+                    for (psS32 r = 0; r < numInputs ; r++) {
+                        tmpPixels->data.F32[r]*= scale->data.F32[r];
+                    }
+                }
+
+                // We add the zero vector, if non-NULL.
+                if (zero != NULL) {
+                    for (psS32 r = 0; r < numInputs ; r++) {
+                        tmpPixels->data.F32[r]+= zero->data.F32[r];
+                    }
+                }
+
+                // Calculate the specified statistic on the stack of pixels.
+                //                for (psS32 r = 0; r < numInputs ; r++) printf("(2)tmpPixels->data.F32[%d] is %f\n", r, tmpPixels->data.F32[r]);
+                psStats *rc = psVectorStats(stats,
+                                            tmpPixels,
+                                            NULL,
+                                            tmpPixelMaskNKeep,
+                                            1);
+                if (rc == NULL) {
+                    psError(PS_ERR_UNKNOWN, false, "psVectorStats(): could not perform requested statistical operation.  Returning NULL.\n");
+                    return(NULL);
+                }
+            }
+
+
+            // Set the pixel value in the output image to the stat value.
+            double statValue;
+            if (!p_psGetStatValue(stats, &statValue)) {
+                psError(PS_ERR_UNKNOWN, true, "Could not determine stats value.  Returning NULL.\n");
+                return(NULL);
+            } else {
+                output->data.F32[i-output->row0][j-output->col0] = (psF32) statValue;
+            }
+        }
+    }
+
+    psFree(tmpPixels);
+    psFree(tmpPixelErrors);
+    psFree(tmpPixelMask);
+    psFree(tmpPixelMaskNKeep);
+    psFree(outRowLower);
+    psFree(outRowUpper);
+    psFree(outColLower);
+    psFree(outColUpper);
+    psFree(tmpReadouts);
+
+    return(output);
+}
+
+
+/* This function measures the robust median at each of the minimum and maximum
+ * coordinates and determines the difference and mean of the two values. The size
+ * of the box used to make the measurement at each point is specified by the
+ * configuration variable FRINGE_SQUARE_RADIUS. From the collection of
+ * differences, the robust median is calculated, and returned as part of the
+ * fringe statistics. For each fringe point, the values of delta and midValue are
+ * also assigned and available to the user on return.
+ */
+
+psStats *pmFringeStats(
+    psArray *fringePoints,
+    psImage *image,
+    psMetadata *config)
+{
+    PS_ASSERT_PTR_NON_NULL(fringePoints, NULL);
+    //    for (psS32 i = 0 ; i < fringePoints->n ; i++) {
+    //        if (!psMemCheckFringePoint((pmFringePoint *) fringePoints->data[i])) {
+    //            psError(PS_ERR_UNKNOWN, true, "fringePoints->data[%d] is not of type pmFringePoint.\n");
+    //            return(NULL);
+    //        }
+    //    }
+    PS_ASSERT_IMAGE_NON_NULL(image, NULL);
+    PS_ASSERT_IMAGE_NON_EMPTY(image, NULL);
+    PS_ASSERT_IMAGE_TYPE(image, PS_TYPE_F32, NULL);
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+
+    psBool rc;
+    psF32 frSquareRadius = psMetadataLookupF32(&rc, config, "FRINGE_SQUARE_RADIUS");
+    if (!rc) {
+        psError(PS_ERR_UNKNOWN, true, "Could not determing the fringe radius from the metadata.\n");
+    }
+
+    psRegion minRegion;
+    psRegion maxRegion;
+    psStats *minStats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN);
+    psStats *maxStats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN);
+    psStats *diffStats = psStatsAlloc(PS_STAT_ROBUST_MEDIAN);
+    psVector *diffs = psVectorAlloc(fringePoints->n, PS_TYPE_F32);
+    diffs->n = diffs->nalloc;
+
+    //
+    // Loop through each fringe point.  Determine the robust mean around
+    // the min and max for that fringe point.  Save the difference between
+    // those two numbers in psVector diffs.
+    //
+    // XXX: Ensure you have the radius correct.  (add 1 to x1 and y1?)
+    //
+    for (psS32 i = 0 ; i < fringePoints->n ; i++) {
+        pmFringePoint *fp = (pmFringePoint *) fringePoints->data[i];
+        minRegion.x0 = fp->xMin - frSquareRadius;
+        minRegion.x1 = fp->xMin + frSquareRadius;
+        minRegion.y0 = fp->yMin - frSquareRadius;
+        minRegion.y1 = fp->yMin + frSquareRadius;
+        psImage *minSubImage = psImageSubset(image, minRegion);
+        minStats = psImageStats(minStats, minSubImage, NULL, 0);
+
+        maxRegion.x0 = fp->xMax - frSquareRadius;
+        maxRegion.x1 = fp->xMax + frSquareRadius;
+        maxRegion.y0 = fp->yMax - frSquareRadius;
+        maxRegion.y1 = fp->yMax + frSquareRadius;
+        psImage *maxSubImage = psImageSubset(image, maxRegion);
+        maxStats = psImageStats(maxStats, maxSubImage, NULL, 0);
+
+        if ((minStats == NULL) || (maxStats == NULL)) {
+            psError(PS_ERR_UNKNOWN, true, "Could not determine robust mean on subimage.\n");
+            psFree(minStats);
+            psFree(maxStats);
+            return(NULL);
+        }
+
+        fp->midValue = 0.5 * (maxStats->robustMedian + minStats->robustMedian);
+        fp->delta = maxStats->robustMedian - minStats->robustMedian;
+        diffs->data.F32[i] = fp->delta;
+    }
+    psFree(minStats);
+    psFree(maxStats);
+
+    diffStats = psVectorStats(diffStats, diffs, NULL, NULL, 0);
+    psFree(diffs);
+    if (diffStats == NULL) {
+        psError(PS_ERR_UNKNOWN, true, "Could not determine robust median of the differences.\n");
+        return(NULL);
+    }
+    return(diffStats);
+}
+
+/**
+ *
+ * The input array fluxLevels consists of Ni vectors, one per mosaic image.
+ * Each vector consists of Nj elements, each a measurement of the input
+ * flat-field image flux levels. All of these vectors must be constructed with
+ * the same number of elements, or the function will return an error. If a chip
+ * is missing from a particular image, that element should be set to NaN. The
+ * vector chipGains supplies initial guesses for the chip gains. If the vector
+ * contains the values 0.0 or NaN for any of the elements, the gain is set to the
+ * mean of the valid values. If the vector length does not match the number of
+ * chips, an warning is raised, all chip gain guesses will be set to 1.0, and the
+ * vector length modified to match the number of chips defined by the supplied
+ * fluxLevels. The sourceFlux input vector must be allocated (not NULL), but the
+ * routine will set the vector length to the number of source images regardless
+ * of the initial state of the vector. All vectors used by this function must be
+ * of type PS_DATA_F64.
+ *
+ 
+fluxLevels(i, j): for each flat field image i, this psArray contains a vector
+with an elemenmt for each chip j.  So, fluxLevels(i, j) corresponds to the
+measured flux M_(i, j) for flat image i, chip j.
+ 
+chipGains[]: has j elements, one for each chip.
+ 
+ 
+They have the observed flux levels for each chip of each image.  They want to
+solve for the actual flux levels and the gain of each chip.
+ 
+Okay, they want to solve for source fluxes and chip gains.
+ 
+ *
+ */
+bool pmFlatNormalization(
+    psVector *sourceFlux,
+    psVector *chipGains,
+    psArray *fluxLevels)
+{
+    PS_ASSERT_PTR_NON_NULL(fluxLevels, false);
+    psS32 numImages = fluxLevels->n;
+    psS32 numChips = ((psVector *) fluxLevels->data[0])->n;
+    for (psS32 i = 0 ; i < numImages ; i++) {
+        psVector *tmpVec = (psVector *) fluxLevels->data[i];
+        PS_ASSERT_VECTOR_NON_NULL(tmpVec, false);
+        PS_ASSERT_VECTOR_TYPE(tmpVec, PS_TYPE_F64, false);
+        PS_ASSERT_VECTOR_SIZE(tmpVec, numChips, false);
+    }
+
+    //
+    // Ensure that *localChipGains points to a vector of the same length as numImages.
+    //
+    PS_ASSERT_PTR_NON_NULL(chipGains, false);
+    PS_ASSERT_VECTOR_TYPE(chipGains, PS_TYPE_F64, false);
+    psVector *localChipGains = chipGains;
+    if (numChips != chipGains->n) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: the chipGains vector length does not match the number of chips.\n");
+        localChipGains = psVectorAlloc(numChips, PS_TYPE_F64);
+        localChipGains->n = localChipGains->nalloc;
+        psBool rc = psVectorInit(localChipGains, 1.0);
+        if (rc == false) {
+            printf("XXX: gen error\n");
+        }
+    }
+
+    //
+    // If the chipGains vector contains the values 0.0 or NaN for any of the elements,
+    // the gain is set to the mean of the valid values.
+    //
+    psBool meanFlag = false;
+    psVector *chipGainsMask = psVectorAlloc(chipGains->n, PS_TYPE_U8);
+    chipGainsMask->n = chipGainsMask->nalloc;
+    for (psS32 i = 0 ; i < chipGains->n ; i++) {
+        if ((fabs(chipGains->data.F64[i]) < FLT_EPSILON) ||
+                (isnan(chipGains->data.F64[i]))) {
+            chipGainsMask->data.U8[i] = 1;
+            meanFlag = true;
+        }
+    }
+    // Must calculate the mean.
+    if (meanFlag == true) {
+        psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        stats = psVectorStats(stats, chipGains, NULL, chipGainsMask, 1);
+        if (stats == NULL) {
+            printf("XXX: gen error\n");
+        }
+        psF64 mean;
+        psBool rc = p_psGetStatValue(stats, &mean);
+        if (rc == false) {
+            printf("XXX: gen error\n");
+        }
+        // Set the gain to this mean for chips with a gain of 0.0 or NAN
+
+        for (psS32 i = 0 ; i < chipGains->n ; i++) {
+            if ((fabs(chipGains->data.F64[i]) < FLT_EPSILON) ||
+                    (isnan(chipGains->data.F64[i]))) {
+                chipGains->data.F64[i] = mean;
+            }
+        }
+    }
+
+    //
+    // Assert that sourceFlux is non-NULL, correct type, correct size.
+    //
+    PS_ASSERT_PTR_NON_NULL(sourceFlux, false);
+    PS_ASSERT_VECTOR_TYPE(sourceFlux, PS_TYPE_F64, false);
+    psVectorRealloc(sourceFlux, numImages);
+
+    //    psFree(psVector);
+    if (numImages != chipGains->n) {
+        psFree(localChipGains);
+    }
+
+    return(true);
+}
Index: /tags/rel11_ifa_pre0/psModules/src/imcombine/pmReadoutCombine.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/imcombine/pmReadoutCombine.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/imcombine/pmReadoutCombine.h	(revision 6961)
@@ -0,0 +1,76 @@
+/** @file  pmReadoutCombine.h
+ *
+ *  This file will contain a module which will combine multiple readout images.
+ *
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:10:08 $
+ *
+ *  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<stdio.h>
+#include<math.h>
+#include "pslib.h"
+#include "psConstants.h"
+#include "pmFPA.h"
+
+typedef struct
+{
+    psStats *stats;
+    unsigned int maskVal;
+    float fracHigh;
+    float fracLow;
+    int nKeep;
+}
+pmCombineParams;
+
+pmCombineParams *pmCombineParamsAlloc (psStatsOptions statsOptions);
+
+psImage *pmReadoutCombine(psImage *output,
+                          const psArray *inputs,
+                          const psVector *zero,
+                          const psVector *scale,
+                          pmCombineParams *params,
+                          bool applyZeroScale,
+                          float gain,
+                          float readnoise);
+
+/**
+ *
+ * This function measures the robust median at each of the minimum and maximum
+ * coordinates and determines the difference and mean of the two values. The size
+ * of the box used to make the measurement at each point is specified by the
+ * configuration variable FRINGE_SQUARE_RADIUS. From the collection of
+ * differences, the robust median is calculated, and returned as part of the
+ * fringe statistics. For each fringe point, the values of delta and midValue are
+ * also assigned and available to the user on return.
+ *
+ */
+psStats *pmFringeStats(
+    psArray *fringePoints,
+    psImage *image,
+    psMetadata *config
+);
+
+typedef struct
+{
+    psF64 xMin;
+    psF64 yMin;
+    psF64 xMax;
+    psF64 yMax;
+    psF64 delta;
+    psF64 midValue;
+}
+pmFringePoint;
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/imsubtract/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/imsubtract/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/imsubtract/.cvsignore	(revision 6961)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/rel11_ifa_pre0/psModules/src/imsubtract/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/imsubtract/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/imsubtract/Makefile.am	(revision 6961)
@@ -0,0 +1,13 @@
+noinst_LTLIBRARIES = libpsmoduleimsubtract.la
+
+libpsmoduleimsubtract_la_CPPFLAGS = $(SRCINC) $(PSMODULE_CFLAGS)
+libpsmoduleimsubtract_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmoduleimsubtract_la_SOURCES  = pmImageSubtract.c \
+    pmSubtractBias.c
+#    pmSubtractSky.c
+
+psmoduleincludedir = $(includedir)
+psmoduleinclude_HEADERS = \
+    pmImageSubtract.h \
+    pmSubtractBias.h
+#    pmSubtractSky.h
Index: /tags/rel11_ifa_pre0/psModules/src/imsubtract/pmImageSubtract.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/imsubtract/pmImageSubtract.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/imsubtract/pmImageSubtract.c	(revision 6961)
@@ -0,0 +1,1407 @@
+/** @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.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-03-04 01:01:33 $
+ *
+ *  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.
+ */
+
+#include<stdio.h>
+#include<math.h>
+#include "pslib.h"
+#include "psConstants.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("ImageSubtract.pmSubtractionKernelsAllocPOIS", 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("ImageSubtract.pmSubtractionKernelsAllocPOIS", 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("ImageSubtract.pmSubtractionKernelsAllocISIS", 3,
+            "Calling pmSubtractionKernelsAllocISIS(%d, %d, %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);
+                    PS_IMAGE_SET_F32(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("ImageSubtract.pmSubtractionKernelsAllocISIS", 3,
+            "Exiting pmSubtractionKernelsAllocISIS(%d, %d, %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("ImageSubtract.pmSubtractionFindStamps", 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("ImageSubtract.pmSubtractionFindStamps", 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("ImageSubtract.GenSpatialOrder", 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("ImageSubtract.GenSpatialOrder", 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("ImageSubtract.IsisKernelConvolve", 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("ImageSubtract.IsisKernelConvolve", 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("ImageSubtract.ConvolvePixelPois", 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("ImageSubtract.ConvolvePixelPois", 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("ImageSubtract.ConvolvePixelIsis", 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("ImageSubtract.ConvolvePixelIsis", 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("ImageSubtract.ConvolveImage", 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, 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("ImageSubtract.ConvolveImage", 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("ImageSubtract.pmSubtractionCalculateEquation", 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("pmSubtractionCalculateEquation", 6, "subCalcEqn(): stamp %d\n", s);
+        if (stamp->status == PM_STAMP_RECALC) {
+            psTrace("pmSubtractionCalculateEquation", 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);
+            }
+            PS_IMAGE_SET_F64(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, numSolveParams, false);
+            }
+            PS_VECTOR_SET_F64(stampVector, 0.0);
+            psTrace("pmSubtractionCalculateEquation", 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("pmSubtractionCalculateEquation", 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("pmSubtractionCalculateEquation", 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("pmSubtractionCalculateEquation", 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("ImageSubtract.pmSubtractionCalculateEquation", 3,
+            "Exiting pmSubtractionCalculateEquation()\n");
+    return(true);
+}
+
+
+
+
+/*******************************************************************************
+ ******************************************************************************/
+psVector *pmSubtractionSolveEquation(psVector *solution, ///< Solution vector, or NULL
+                                     const psArray *stamps      ///< Array of stamps
+                                    )
+{
+    psTrace("ImageSubtract.pmSubtractionSolveEquation", 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, 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;
+    PS_VECTOR_SET_F64(sumVector, 0.0);
+    PS_IMAGE_SET_F64(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, 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("ImageSubtract.pmSubtractionSolveEquation", 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("ImageSubtract.CalculateDeviations", 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.
+        PS_VECTOR_SET_F32(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, 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("ImageSubtract.CalculateDeviations", 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("ImageSubtract.pmSubtractionRejectStamps", 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, 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("ImageSubtract.pmSubtractionRejectStamps", 3,
+            "Exiting pmSubtractionRejectStamps()\n");
+    return(true);
+}
+
+/*******************************************************************************
+ ******************************************************************************/
+psImage *pmSubtractionKernelImage(psImage *out,
+                                  const psVector *solution,
+                                  const psSubtractionKernels *kernels,
+                                  psF32 x,
+                                  psF32 y
+                                 )
+{
+    psTrace("ImageSubtract.pmSubtractionKernelImage", 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);
+    }
+    PS_IMAGE_SET_F32(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("ImageSubtract.pmSubtractionKernelImage", 3,
+            "Exiting pmSubtractionKernelImage()\n");
+    return(out);
+}
Index: /tags/rel11_ifa_pre0/psModules/src/imsubtract/pmImageSubtract.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/imsubtract/pmImageSubtract.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/imsubtract/pmImageSubtract.h	(revision 6961)
@@ -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: 2005-09-28 20:43:52 $
+ *
+ *  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/rel11_ifa_pre0/psModules/src/imsubtract/pmSubtractBias.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/imsubtract/pmSubtractBias.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/imsubtract/pmSubtractBias.c	(revision 6961)
@@ -0,0 +1,577 @@
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// 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.12 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:10:08 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.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
+
+    // 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;
+}
+
+
+#if 0
+/******************************************************************************
+ImageSubtractScalar(): subtract a scalar from the input image.
+ 
+XXX: Use a psLib function for this.
+ 
+XXX: This should
+ *****************************************************************************/
+static psImage *ImageSubtractScalar(psImage *image,
+                                    psF32 scalar)
+{
+    for (psS32 i=0;i<image->numRows;i++) {
+        for (psS32 j=0;j<image->numCols;j++) {
+            image->data.F32[i][j]-= scalar;
+        }
+    }
+    return(image);
+}
+#endif
+
+/******************************************************************************
+GenNewStatOptions(): this routine will take as input the options member of the
+stat data structure, determine if multiple options have been specified, issue
+a warning message if so, and return the highest priority option (according to
+the order of the if-statements in this code).  The higher priority options are
+listed lower in the code.
+ *****************************************************************************/
+static psStatsOptions GenNewStatOptions(const psStats *stat)
+{
+    psS32 numOptions = 0;
+    psStatsOptions opt = 0;
+
+    if (stat->options & PS_STAT_ROBUST_MEDIAN) {
+        if (numOptions == 0) {
+            opt = PS_STAT_ROBUST_MEDIAN;
+        }
+        numOptions++;
+    }
+
+    if (stat->options & PS_STAT_CLIPPED_MEAN) {
+        if (numOptions == 0) {
+            opt = PS_STAT_CLIPPED_MEAN;
+        }
+        numOptions++;
+    }
+
+    if (stat->options & PS_STAT_SAMPLE_MEDIAN) {
+        if (numOptions == 0) {
+            opt = PS_STAT_SAMPLE_MEDIAN;
+        }
+        numOptions++;
+    }
+
+    if (stat->options & PS_STAT_SAMPLE_MEAN) {
+        numOptions++;
+        opt = PS_STAT_SAMPLE_MEAN;
+    }
+
+
+    if (numOptions == 0) {
+        psError(PS_ERR_UNKNOWN,true, "No statistics options have been specified.\n");
+    }
+    if (numOptions != 1) {
+        psLogMsg(__func__, PS_LOG_WARN,
+                 "WARNING: pmSubtractBias.c: GenNewStatOptions(): Too many statistics options have been specified\n");
+    }
+    return(opt);
+}
+
+
+
+#if 0
+/******************************************************************************
+ScaleOverscanVector(): this routine takes as input an arbitrary vector,
+creates a new vector of length n, and fills the new vector with the
+interpolated values of the old vector.  The type of interpolation is:
+    PM_FIT_POLYNOMIAL: fit a polynomial to the entire input vector data.
+    PM_FIT_SPLINE: fit splines to the input vector data.
+XXX: Doesn't it make more sense to do polynomial interpolation on a few
+elements of the input vector, rather than fit a polynomial to the entire
+vector?
+ *****************************************************************************/
+static psVector *ScaleOverscanVector(psVector *overscanVector,
+                                     psS32 n,
+                                     void *fitSpec,
+                                     pmFit fit)
+{
+    psTrace(".psModule.pmSubtracBias.ScaleOverscanVector", 4,
+            "---- ScaleOverscanVector() begin (%d -> %d) ----\n", overscanVector->n, n);
+    //    PS_VECTOR_PRINT_F32(overscanVector);
+
+    if (NULL == overscanVector) {
+        return(overscanVector);
+    }
+
+    // Allocate the new vector.
+    psVector *newVec = psVectorAlloc(n, PS_TYPE_F32);
+
+    //
+    // If the new vector is the same size as the old, simply copy the data.
+    //
+    if (n == overscanVector->n) {
+        for (psS32 i = 0 ; i < n ; i++) {
+            newVec->data.F32[i] = overscanVector->data.F32[i];
+        }
+        return(newVec);
+    }
+    psPolynomial1D *myPoly;
+    psSpline1D *mySpline;
+    psF32 x;
+    psS32 i;
+    if (fit == PM_FIT_POLYNOMIAL) {
+        // Fit a polynomial to the old overscan vector.
+        myPoly = (psPolynomial1D *) fitSpec;
+        PS_ASSERT_POLY_NON_NULL(myPoly, NULL);
+        myPoly = psVectorFitPolynomial1D(myPoly, NULL, 0, overscanVector, NULL, NULL);
+        if (myPoly == NULL) {
+            psError(PS_ERR_UNKNOWN, false, "ScaleOverscanVector()(1): Could not fit a polynomial to the psVector.\n");
+            return(NULL);
+        }
+
+        // For each element of the new vector, convert the x-ordinate to that
+        // of the old vector, use the fitted polynomial to determine the
+        // interpolated value at that point, and set the new vector.
+        for (i=0;i<n;i++) {
+            x = ((psF32) i) * ((psF32) overscanVector->n) / ((psF32) n);
+            newVec->data.F32[i] = psPolynomial1DEval(myPoly, x);
+        }
+    } else if (fit == PM_FIT_SPLINE) {
+        psS32 mustFreeSpline = 0;
+        // Fit a spline to the old overscan vector.
+        mySpline = (psSpline1D *) fitSpec;
+        // XXX: Does it make any sense to have a psSpline argument?
+        if (mySpline == NULL) {
+            mustFreeSpline = 1;
+        }
+
+        //
+        // NOTE: Since the X arg in the psVectorFitSpline1D() function is NULL,
+        // splines endpoints will be from 0.0 to overscanVector->n-1.  Must scale
+        // properly when doing the spline eval.
+        //
+        //        mySpline = psVectorFitSpline1D(mySpline, NULL, overscanVector, NULL);
+        mySpline = psVectorFitSpline1D(NULL, overscanVector);
+        if (mySpline == NULL) {
+            psError(PS_ERR_UNKNOWN, false, "ScaleOverscanVector()(2): Could not fit a spline to the psVector.\n");
+            return(NULL);
+        }
+        //        PS_PRINT_SPLINE(mySpline);
+
+        // For each element of the new vector, convert the x-ordinate to that
+        // of the old vector, use the fitted polynomial to determine the
+        // interpolated value at that point, and set the new vector.
+        for (i=0;i<n;i++) {
+            // Scale to [0 : overscanVector->n - 1]
+            x = ((psF32) i) * ((psF32) (overscanVector->n-1)) / ((psF32) n);
+            newVec->data.F32[i] = psSpline1DEval(mySpline, x);
+        }
+        if (mustFreeSpline ==1) {
+            psFree(mySpline);
+        }
+        //        PS_VECTOR_PRINT_F32(newVec);
+
+
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "unknown fit type.  Returning NULL.\n");
+        psFree(newVec);
+        return(NULL);
+    }
+
+    psTrace(".psModule.pmSubtracBias.ScaleOverscanVector", 4,
+            "---- ScaleOverscanVector() exit ----\n");
+    return(newVec);
+}
+
+#endif
+
+// 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
+                               )
+{
+    // 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
+    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);
+            double reducedVal = NAN; // Result of statistics
+            if (! p_psGetStatValue(myStats, &reducedVal)) {
+                psError(PS_ERR_UNKNOWN, false, "p_psGetStatValue(): could not determine result "
+                        "of statistics on row %d.\n", i);
+                return NULL;
+            }
+            reduced->data.F32[i] = reducedVal;
+        } 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.
+ *****************************************************************************/
+pmReadout *pmSubtractBias(pmReadout *in, pmOverscanOptions *overscanOpts,
+                          const pmReadout *bias, const pmReadout *dark)
+{
+    psTrace(".psModule.pmSubtracBias.pmSubtractBias", 4,
+            "---- pmSubtractBias() begin ----\n");
+    PS_ASSERT_READOUT_NON_NULL(in, NULL);
+    PS_ASSERT_READOUT_NON_EMPTY(in, NULL);
+    PS_ASSERT_READOUT_TYPE(in, 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(in);
+        }
+
+        psList *overscans = in->bias; // List of the overscan images
+
+        psStats *myStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN); // A new psStats, to avoid clobbering original
+        myStats->options = GenNewStatOptions(overscanOpts->stat);
+
+        // Reduce all overscan pixels to a single value
+        if (overscanOpts->single) {
+            psVector *pixels = psVectorAlloc(0, PS_TYPE_F32);
+            pixels->n = 0;
+            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(myStats, pixels, NULL, NULL, 0);
+            double reduced = NAN;     // Result of statistics
+            if (! p_psGetStatValue(myStats, &reduced)) {
+                psError(PS_ERR_UNKNOWN, false, "p_psGetStatValue(): could not determine result from requested statistical operation.  Returning input image.\n");
+                return(in);
+            }
+            (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
+                for (int i = 0; i < pixels->n; i++) {
+                    psVector *values = psVectorAlloc(0, PS_TYPE_F32);
+                    values->n = 0;
+                    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, myStats);
+                psFree(pixels);
+                if (! reduced) {
+                    return in;
+                }
+
+                // 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
+                for (int i = 0; i < pixels->n; i++) {
+                    psVector *values = psVectorAlloc(0, PS_TYPE_F32);
+                    values->n = 0;
+                    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, myStats);
+                psFree(pixels);
+                if (! reduced) {
+                    return in;
+                }
+
+                // 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(myStats);
+    } // End of overscan subtraction
+
+    // Bias frame subtraction
+    if (bias) {
+        SubtractFrame(in, bias, 1.0);
+    }
+
+    if (dark) {
+        // Get the scaling
+        float inTime = psMetadataLookupF32(NULL, in->parent->concepts, "CELL.DARKTIME");
+        float darkTime = psMetadataLookupF32(NULL, dark->parent->concepts, "CELL.DARKTIME");
+        SubtractFrame(in, dark, inTime/darkTime);
+    }
+
+    return in;
+}
+
+
Index: /tags/rel11_ifa_pre0/psModules/src/imsubtract/pmSubtractBias.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/imsubtract/pmSubtractBias.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/imsubtract/pmSubtractBias.h	(revision 6961)
@@ -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.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:10:08 $
+ *
+ *  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);
+
+pmReadout *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/rel11_ifa_pre0/psModules/src/imsubtract/pmSubtractSky.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/imsubtract/pmSubtractSky.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/imsubtract/pmSubtractSky.c	(revision 6961)
@@ -0,0 +1,741 @@
+/** @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.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-03-04 01:01:33 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ *
+ *
+ */
+
+#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("SubtractSky.DetermineNumBits", 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("SubtractSky.DetermineNumBits", 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("SubtractSky.getHighestPriorityStatOption", 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.
+ *****************************************************************************/
+/*
+static psImage *binImage(psImage *origImage,
+                         int binFactor,
+                         psStatsOptions statOptions)
+{
+    psTrace("SubtractSky.binImage", 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("SubtractSky.binImage", 4, "Exiting binImage(%d)\n", binFactor);
+    return(origImage);
+}
+*/
+
+/******************************************************************************
+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("SubtractSky.CalculatePolyTerms", 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("SubtractSky.CalculatePolyTerms", 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("SubtractSky.buildPolyTerms", 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("SubtractSky.buildPolyTerms", 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("SubtractSky.buildPolyTerms", 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("SubtractSky.buildPolyTerms", 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("SubtractSky.ImageFitPolynomial", 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.
+    //
+    PS_IMAGE_SET_F64(A, 0.0);
+    PS_VECTOR_SET_F64(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(".psModule.pmSubtractSky.ImageFitPolynomial", 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("SubtractSky.ImageFitPolynomial", 4,
+            "Exiting ImageFitPolynomial()\n");
+    //    psTrace(".psModule.pmSubtractSky.ImageFitPolynomial", 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(".psModule.pmSubtractSky", 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);
+            PS_IMAGE_SET_U8(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);
+        PS_IMAGE_SET_U8(binnedMaskImage, 0);
+    }
+    psTrace(".psModule.pmSubtractSky", 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.
+        psF64 binnedMean;
+        psF64 binnedStdev;
+        psStats *myStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        psStats *rc =  psImageStats(myStats, binnedImage, NULL, 0);
+        if (rc == NULL) {
+            psError(PS_ERR_UNKNOWN, false, "psImageStats(): could not perform requested statistical operation.  Returning in image.\n");
+            return(in);
+        }
+        if (false == p_psGetStatValue(rc, &binnedMean)) {
+            psError(PS_ERR_UNKNOWN, false, "p_psGetStatValue(): could not determine requested statistical operation.  Returning in image.\n");
+            return(in);
+        }
+        psTrace(".psModule.pmSubtractSky", 6,
+                "binned Mean is %f\n", binnedMean);
+
+        myStats->options = PS_STAT_SAMPLE_STDEV;
+        rc =  psImageStats(myStats, binnedImage, NULL, 0);
+        if (rc == NULL) {
+            psError(PS_ERR_UNKNOWN, false, "psImageStats(): could not perform requested statistical operation.  Returning in image.\n");
+            return(in);
+        }
+        if (false == p_psGetStatValue(myStats, &binnedStdev)) {
+            psError(PS_ERR_UNKNOWN, false, "p_psGetStatValue(): could not determine requested statistical operation.  Returning in image.\n");
+            return(in);
+        }
+        psFree(myStats);
+        psTrace(".psModule.pmSubtractSky", 6,
+                "binned StDev is %f\n", binnedStdev);
+
+        // Clip all pixels which are more than clipSD sigmas from the mean.
+        psTrace(".psModule.pmSubtractSky", 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(".psModule.pmSubtractSky", 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(".psModule.pmSubtractSky", 4,
+            "---- pmSubtractSky() exit successfully ----\n");
+    return(in);
+}
Index: /tags/rel11_ifa_pre0/psModules/src/imsubtract/pmSubtractSky.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/imsubtract/pmSubtractSky.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/imsubtract/pmSubtractSky.h	(revision 6961)
@@ -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.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:10:08 $
+ *
+ *  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/rel11_ifa_pre0/psModules/src/mainpage.dox
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/mainpage.dox	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/mainpage.dox	(revision 6961)
@@ -0,0 +1,127 @@
+/** @mainpage psModule Image Processing Library
+
+
+@section intro Introduction
+This library contains the Pan-STARRS Image Processing Pipeline (IPP) modules (psModule). 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 psModule 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 psModule 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 psModule.  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 psModule 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 psModule Library.
+
+The psModule library and associated tests are made via the GNU autoconf/automake system.
+
+The source should build using the configure script in the psModule directory.  The
+recommended steps are:
+<pre>
+$ cd psModule
+$ ./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 psModule 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 psModule library
+
+To assist the use of the library with your own code, a configuration tool is part
+of the psModule library package.  This tool, psmodule-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 psModule can be
+obtained via 'psmodule-config --cflags'.  This outputs the cc options that supplies
+include path(s) required to find the psModule headers.
+
+The required linking options, can be obtained via 'psmodule-config --libs'.  This
+outputs the ld options that supplies the library paths and files required to
+link to the psModule library.
+
+Note: psmodule-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 psModule
+$ 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/rel11_ifa_pre0/psModules/src/objects/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/.cvsignore	(revision 6961)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/rel11_ifa_pre0/psModules/src/objects/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/Makefile.am	(revision 6961)
@@ -0,0 +1,47 @@
+noinst_LTLIBRARIES = libpsmoduleobjects.la
+
+libpsmoduleobjects_la_CPPFLAGS = $(SRCINC) $(PSMODULE_CFLAGS) -I../pslib/
+libpsmoduleobjects_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmoduleobjects_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
+
+EXTRA_DIST = \
+	models/pmModel_GAUSS.c \
+	models/pmModel_PGAUSS.c \
+	models/pmModel_QGAUSS.c \
+	models/pmModel_SGAUSS.c 
+
+psmoduleincludedir = $(includedir)
+psmoduleinclude_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
Index: /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_GAUSS.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_GAUSS.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_GAUSS.c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/objects/models/pmModel_PGAUSS.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_PGAUSS.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_PGAUSS.c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/objects/models/pmModel_QGAUSS.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_QGAUSS.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_QGAUSS.c	(revision 6961)
@@ -0,0 +1,262 @@
+
+/******************************************************************************
+    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] =
+*****************************************************************************/
+
+psF32 pmModelFunc_QGAUSS(psVector *deriv,
+                         const psVector *params,
+                         const psVector *x)
+{
+    psF32 *PAR = params->data.F32;
+
+    psF32 X  = x->data.F32[0] - PAR[2];
+    psF32 Y  = x->data.F32[1] - PAR[3];
+    psF32 px = PAR[4]*X;
+    psF32 py = PAR[5]*Y;
+    psF32 z  = 0.5*PS_SQR(px) + 0.5*PS_SQR(py) + PAR[6]*X*Y;
+    if (z < 0)
+        z = 0;
+
+    psF32 zp = pow(z,1.25);
+    psF32 r  = 1.0 / (1 + PAR[7]*z + z*zp);
+
+    psF32 r1 = PAR[1]*r;
+    psF32 f  = r1 + PAR[0];
+
+    if (deriv != NULL) {
+        psF32 *dPAR = deriv->data.F32;
+
+        // note difference from a pure gaussian: q = params->data.F32[1]*r
+        psF32 t = r1*r;
+        psF32 q = t*(PAR[7] + 2.25*zp);
+
+        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;
+        dPAR[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[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;
+    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] = 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;
+    params_max[0][0].data.F32[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[0] = moments->Sky;
+    params[1] = moments->Peak - moments->Sky;
+    params[2] = peak->x;
+    params[3] = peak->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;
+    params[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[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_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[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 limit = flux / PAR[1];
+
+    # 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[7]*z + pow(z, 2.25));
+        // test: f = 1.0 / (1 + 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[7];
+    z1 = PS_MAX (z0, z1);
+    z0 = 0.0;
+
+    float f0 = 1.0 / (1 + PAR[7]*z0 + pow(z0, 2.25));
+    float f1 = 1.0 / (1 + 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[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[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];
+        // 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_QGAUSS (pmModel *model)
+{
+
+    psF32 dP;
+    bool  status;
+
+    psF32 *PAR  = model->params->data.F32;
+    psF32 *dPAR = model->dparams->data.F32;
+
+    dP = 0;
+    dP += PS_SQR(dPAR[4] / PAR[4]);
+    dP += PS_SQR(dPAR[5] / PAR[5]);
+    dP = sqrt (dP);
+
+    status = true;
+    status &= (dP < 0.5);
+    status &= (PAR[1] > 0);
+    status &= ((dPAR[1]/PAR[1]) < 0.5);
+
+    if (!status)
+        return false;
+    return true;
+}
Index: /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_RGAUSS.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_RGAUSS.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_RGAUSS.c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/objects/models/pmModel_SGAUSS.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_SGAUSS.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_SGAUSS.c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/objects/models/pmModel_TGAUSS.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_TGAUSS.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_TGAUSS.c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/objects/models/pmModel_WAUSS.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_WAUSS.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_WAUSS.c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/objects/models/pmModel_ZGAUSS.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_ZGAUSS.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/models/pmModel_ZGAUSS.c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/objects/pmGrowthCurve.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmGrowthCurve.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmGrowthCurve.c	(revision 6961)
@@ -0,0 +1,131 @@
+/** @file  pmGrowthCurve.c
+ *
+ *  Measure the curve-of-growth for sources
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-21 21:25:52 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#include <pslib.h>
+#include "pmGrowthCurve.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;
+}
+
+// return the last entry below or first entry above key value
+int psVectorBracket (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);
+}
+
+// search for the bins bounding key in index, interpolate the corresponding values
+psF32 psVectorInterpolate (psVector *index, 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/rel11_ifa_pre0/psModules/src/objects/pmGrowthCurve.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmGrowthCurve.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmGrowthCurve.h	(revision 6961)
@@ -0,0 +1,24 @@
+
+# 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);
+int psVectorBracket (psVector *index, psF32 key, bool above);
+psF32 psVectorInterpolate (psVector *index, psVector *value, psF32 key);
+psF32 pmGrowthCurveCorrect (pmGrowthCurve *growth, psF32 radius);
+
+// XXX psVectorBracket,Interpolate should be put in pslib
+
+# endif /* PM_GROWTH_CURVE_H */
Index: /tags/rel11_ifa_pre0/psModules/src/objects/pmModel.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmModel.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmModel.c	(revision 6961)
@@ -0,0 +1,228 @@
+/** @file  pmModel.c
+ *
+ *  Functions to define and manipulate object models
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-21 21:26:01 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#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(__func__, 4, "---- %s() begin ----\n", __func__);
+    psFree(tmp->params);
+    psFree(tmp->dparams);
+    psTrace(__func__, 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(__func__, 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(__func__, 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(__func__, 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(__func__, 3, "---- %s() end ----\n", __func__);
+    return(tmpF);
+}
+
+bool AddOrSubModel(psImage *image,
+                   psImage *mask,
+                   pmModel *model,
+                   bool center,
+                   bool sky,
+                   bool add
+                      )
+{
+    psTrace(__func__, 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(__func__, 3, "---- %s(true) end ----\n", __func__);
+    return(true);
+}
+
+/******************************************************************************
+ *****************************************************************************/
+bool pmModelAdd(psImage *image,
+                psImage *mask,
+                pmModel *model,
+                bool center,
+                bool sky)
+{
+    psTrace(__func__, 3, "---- %s() begin ----\n", __func__);
+    psBool rc = AddOrSubModel(image, mask, model, center, sky, true);
+    psTrace(__func__, 3, "---- %s(%d) end ----\n", __func__, rc);
+    return(rc);
+}
+
+/******************************************************************************
+ *****************************************************************************/
+bool pmModelSub(psImage *image,
+                psImage *mask,
+                pmModel *model,
+                bool center,
+                bool sky)
+{
+    psTrace(__func__, 3, "---- %s() begin ----\n", __func__);
+    psBool rc = AddOrSubModel(image, mask, model, center, sky, false);
+    psTrace(__func__, 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/rel11_ifa_pre0/psModules/src/objects/pmModel.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmModel.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmModel.h	(revision 6961)
@@ -0,0 +1,113 @@
+/** @file  pmObjects.h
+ *
+ *  Functions to define and manipulate object models
+ *
+ *  @author GLG, MHPCC
+ *  @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_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;
+
+/** 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/rel11_ifa_pre0/psModules/src/objects/pmModelGroup.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmModelGroup.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmModelGroup.c	(revision 6961)
@@ -0,0 +1,213 @@
+/** @file  pmModelGroup.c
+ *
+ *  Functions to define and manipulate object model attributes
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:10:08 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include "pslib.h"
+#include "psEllipse.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;
+}
+
+void pmModelGroupInit (void)
+{
+
+    int Nnew = sizeof (defaultModels) / sizeof (pmModelGroup);
+
+    models = pmModelGroupAlloc (Nnew);
+    for (int i = 0; i < Nnew; i++) {
+        models[i] = defaultModels[i];
+    }
+    Nmodels = Nnew;
+    return;
+}
+
+void pmModelGroupCleanup (void)
+{
+
+    psFree (models);
+    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(__func__, 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(__func__, 3, "---- %s() end ----\n", __func__);
+    return(model);
+}
+
Index: /tags/rel11_ifa_pre0/psModules/src/objects/pmModelGroup.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmModelGroup.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmModelGroup.h	(revision 6961)
@@ -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.4 $ $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_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
+void 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/rel11_ifa_pre0/psModules/src/objects/pmMoments.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmMoments.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmMoments.c	(revision 6961)
@@ -0,0 +1,39 @@
+/** @file  pmMoments.c
+ *
+ *  Functions defining the pmMoments structure
+ *
+ *  @author GLG, MHPCC
+ *  @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
+ *
+ */
+
+#include "pslib.h"
+#include "pmMoments.h"
+
+/******************************************************************************
+pmMomentsAlloc(): Allocate the pmMoments structure and initialize the members
+to zero.
+*****************************************************************************/
+pmMoments *pmMomentsAlloc()
+{
+    psTrace(__func__, 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(__func__, 3, "---- %s() end ----\n", __func__);
+    return(tmp);
+}
+
Index: /tags/rel11_ifa_pre0/psModules/src/objects/pmMoments.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmMoments.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmMoments.h	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/objects/pmObjects.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmObjects.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmObjects.c	(revision 6961)
@@ -0,0 +1,21 @@
+/** @file  pmObjects.c
+ *
+ *  This file will ...
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA: significant modifications.
+ *
+ *  @version $Revision: 1.11 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:10:08 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include "pslib.h"
+#include "pmObjects.h"
+#include "pmModelGroup.h"
+
Index: /tags/rel11_ifa_pre0/psModules/src/objects/pmObjects.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmObjects.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmObjects.h	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/objects/pmPSF.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmPSF.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmPSF.c	(revision 6961)
@@ -0,0 +1,316 @@
+/** @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.7 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-23 01:07:51 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+/*****************************************************************************/
+/* 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, 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 (pmPSF *psf, pmPSF_ApTrendOptions option)
+{
+
+    switch (option) {
+    case PM_PSF_NONE:
+        maskAllTerms (psf->ApTrend);
+        return true;
+
+    case PM_PSF_CONSTANT:
+        maskAllTerms (psf->ApTrend);
+        psf->ApTrend->mask[0][0][0][0] = 0;  // unmask constant
+        return true;
+
+    case PM_PSF_SKYBIAS:
+        maskAllTerms (psf->ApTrend);
+        psf->ApTrend->mask[0][0][0][0] = 0;  // unmask constant
+        psf->ApTrend->mask[0][0][1][0] = 0;  // unmask skybias
+        return true;
+
+    case PM_PSF_SKYSAT:
+        maskAllTerms (psf->ApTrend);
+        psf->ApTrend->mask[0][0][0][0] = 0;  // unmask constant
+        psf->ApTrend->mask[0][0][1][0] = 0;  // unmask skybias
+        psf->ApTrend->mask[0][0][0][1] = 0;  // unmask skybias
+        return true;
+
+    case PM_PSF_XY_LIN:
+        maskAllTerms (psf->ApTrend);
+        psf->ApTrend->mask[0][0][0][0] = 0;  // unmask constant
+        psf->ApTrend->mask[1][0][0][0] = 0;  // unmask x
+        psf->ApTrend->mask[0][1][0][0] = 0;  // unmask y
+        return true;
+
+    case PM_PSF_XY_QUAD:
+        maskAllTerms (psf->ApTrend);
+        psf->ApTrend->mask[0][0][0][0] = 0;  // unmask constant
+        psf->ApTrend->mask[1][0][0][0] = 0;  // unmask x
+        psf->ApTrend->mask[2][0][0][0] = 0;  // unmask x^2
+        psf->ApTrend->mask[1][1][0][0] = 0;  // unmask x y
+        psf->ApTrend->mask[0][1][0][0] = 0;  // unmask y
+        psf->ApTrend->mask[0][2][0][0] = 0;  // unmask y^2
+        return true;
+
+    case PM_PSF_SKY_XY_LIN:
+        maskAllTerms (psf->ApTrend);
+        psf->ApTrend->mask[0][0][0][0] = 0;  // unmask constant
+        psf->ApTrend->mask[1][0][0][0] = 0;  // unmask x
+        psf->ApTrend->mask[0][1][0][0] = 0;  // unmask y
+        psf->ApTrend->mask[0][0][1][0] = 0;  // unmask skybias
+        return true;
+
+    case PM_PSF_SKYSAT_XY_LIN:
+        maskAllTerms (psf->ApTrend);
+        psf->ApTrend->mask[0][0][0][0] = 0;  // unmask constant
+        psf->ApTrend->mask[1][0][0][0] = 0;  // unmask x
+        psf->ApTrend->mask[0][1][0][0] = 0;  // unmask y
+        psf->ApTrend->mask[0][0][1][0] = 0;  // unmask skybias
+        psf->ApTrend->mask[0][0][0][1] = 0;  // unmask skysat
+        return true;
+
+    case PM_PSF_ALL:
+    default:
+        maskAllTerms (psf->ApTrend);
+        psf->ApTrend->mask[0][0][0][0] = 0;  // unmask constant
+        psf->ApTrend->mask[0][0][1][0] = 0;  // unmask skybias
+        psf->ApTrend->mask[0][0][0][1] = 0;  // unmask skysat
+
+        psf->ApTrend->mask[1][0][0][0] = 0;  // unmask x
+        psf->ApTrend->mask[2][0][0][0] = 0;  // unmask x^2
+        psf->ApTrend->mask[1][1][0][0] = 0;  // unmask x y
+        psf->ApTrend->mask[0][1][0][0] = 0;  // unmask y
+        psf->ApTrend->mask[0][2][0][0] = 0;  // unmask y^2
+        return true;
+    }
+    return false;
+}
Index: /tags/rel11_ifa_pre0/psModules/src/objects/pmPSF.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmPSF.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmPSF.h	(revision 6961)
@@ -0,0 +1,104 @@
+/** @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
+    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;
+}
+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 (pmPSF *psf, pmPSF_ApTrendOptions option);
+
+# endif
Index: /tags/rel11_ifa_pre0/psModules/src/objects/pmPSF_IO.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmPSF_IO.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmPSF_IO.c	(revision 6961)
@@ -0,0 +1,312 @@
+/** @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.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-17 18:01:05 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+/*****************************************************************************/
+/* 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)
+{
+
+    int Nfail;
+    char *filename;
+
+    switch (file->type) {
+    case PM_FPA_FILE_PSF:
+        filename = pmFPAfileNameFromRule (file->filerule, file, view);
+
+        psMetadata *psfData = psMetadataConfigParse (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/rel11_ifa_pre0/psModules/src/objects/pmPSF_IO.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmPSF_IO.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmPSF_IO.h	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/objects/pmPSFtry.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmPSFtry.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmPSFtry.c	(revision 6961)
@@ -0,0 +1,285 @@
+/** @file  pmPSFtry.c
+ *
+ *  XXX: need description of file purpose
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-23 01:07:51 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+# include <pslib.h>
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmMaskBadPixels.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 sources\n", psTimerMark ("fit"), sources->n);
+    psTrace ("psphot.psftry", 3, "keeping %d of %d 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 sources\n", psTimerMark ("fit"), sources->n);
+    psTrace ("psphot.psftry", 3, "keeping %d of %d PSF candidates (PSF)\n", Npsf, sources->n);
+
+    // measure the chi-square trend as a function of flux (PAR[1])
+    // 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[1];
+            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
+    psVectorClipFitPolynomial1D (psfTry->psf->ChiTrend, stats, mask, 1, chisq, NULL, flux);
+    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));
+    }
+    psLogMsg ("pmPSFtry", 4, "chisq vs flux fit: %f +/- %f\n", stats->sampleMedian, stats->sampleStdev);
+    psFree (stats);
+    psFree (flux);
+    psFree (mask);
+    psFree (chisq);
+
+    // 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);
+    psLogMsg ("pmPSFtryMetric", 4, "fit stats: %f +/- %f\n", stats->sampleMedian, stats->sampleStdev);
+
+    pmPSF_MaskApTrend (psfTry->psf, 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/rel11_ifa_pre0/psModules/src/objects/pmPSFtry.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmPSFtry.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmPSFtry.h	(revision 6961)
@@ -0,0 +1,123 @@
+/** @file  pmPSFtry.h
+ *
+ * This file contains code that allows the user to try to fit several
+ * PSF models to an image.
+ *
+ *  @author EAM, IfA
+ *
+ *
+ *  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 *stars,                     ///< Add comment.
+    char *modelName,                     ///< Add comment.
+    bool poissonErrors   // use poissonian or constant errors?
+);
+
+
+/** 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,                   ///< Add comment.
+    char *modelName,                    ///< Add comment.
+    float radius,                     ///< Add comment.
+    bool poissonErrors   // use poissonian or constant errors?
+);
+
+
+/** 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/rel11_ifa_pre0/psModules/src/objects/pmPeaks.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmPeaks.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmPeaks.c	(revision 6961)
@@ -0,0 +1,545 @@
+/** @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.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-21 21:26:44 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#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(__func__, 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(__func__, 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(__func__, 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(__func__, 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(__func__, 4, "---- %s() begin ----\n", __func__);
+    if ((x >= valid.x0) &&
+            (x <= valid.x1) &&
+            (y >= valid.y0) &&
+            (y <= valid.y1)) {
+        psTrace(__func__, 4, "---- %s(true) end ----\n", __func__);
+        return(true);
+    }
+    psTrace(__func__, 4, "---- %s(false) end ----\n", __func__);
+    return(false);
+}
+
+/******************************************************************************
+pmPeakAlloc(): Allocate the pmPeak data structure and set appropriate members.
+*****************************************************************************/
+pmPeak *pmPeakAlloc(psS32 x,
+                    psS32 y,
+                    psF32 counts,
+                    pmPeakType type)
+{
+    psTrace(__func__, 3, "---- %s() begin ----\n", __func__);
+    pmPeak *tmp = (pmPeak *) psAlloc(sizeof(pmPeak));
+    tmp->x = x;
+    tmp->y = y;
+    tmp->counts = counts;
+    tmp->type = type;
+
+    psTrace(__func__, 3, "---- %s() end ----\n", __func__);
+    return(tmp);
+}
+
+// psSort comparison function for peaks
+int pmPeaksCompareAscend (const void **a, const void **b)
+{
+    psTrace(__func__, 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(__func__, 3, "---- %s(-1) end ----\n", __func__);
+        return (-1);
+    } else if (diff > FLT_EPSILON) {
+        psTrace(__func__, 3, "---- %s(+1) end ----\n", __func__);
+        return (+1);
+    }
+    psTrace(__func__, 3, "---- %s(0) end ----\n", __func__);
+    return (0);
+}
+
+// psSort comparison function for peaks
+int pmPeaksCompareDescend (const void **a, const void **b)
+{
+    psTrace(__func__, 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(__func__, 3, "---- %s(+1) end ----\n", __func__);
+        return (+1);
+    } else if (diff > FLT_EPSILON) {
+        psTrace(__func__, 3, "---- %s(-1) end ----\n", __func__);
+        return (-1);
+    }
+    psTrace(__func__, 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(__func__, 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(__func__, 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(__func__, 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(__func__, 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(__func__, 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(__func__, 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(__func__, 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(__func__, 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(__func__, 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(__func__, 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 %d\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(__func__, 3, "---- %s() end ----\n", __func__);
+    return(output);
+}
Index: /tags/rel11_ifa_pre0/psModules/src/objects/pmPeaks.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmPeaks.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmPeaks.h	(revision 6961)
@@ -0,0 +1,143 @@
+/** @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.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_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
+{
+    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
+);
+
+
+/** 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/rel11_ifa_pre0/psModules/src/objects/pmSource.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSource.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSource.c	(revision 6961)
@@ -0,0 +1,624 @@
+/** @file  pmSource.c
+ *
+ *  Functions to define and manipulate sources on images
+ *
+ *  @author GLG, MHPCC
+ *  @author EAM, IfA: significant modifications.
+ *
+ *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-21 21:29:01 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include "pslib.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmMaskBadPixels.h"
+#include "pmPeaks.h"
+#include "pmMoments.h"
+#include "pmModel.h"
+#include "pmSource.h"
+
+static void sourceFree(pmSource *tmp)
+{
+    psTrace(__func__, 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(__func__, 4, "---- %s() end ----\n", __func__);
+}
+
+/******************************************************************************
+pmSourceAlloc(): Allocate the pmSource structure and initialize its members
+to NULL.
+*****************************************************************************/
+pmSource *pmSourceAlloc()
+{
+    psTrace(__func__, 3, "---- %s() begin ----\n", __func__);
+    pmSource *tmp = (pmSource *) psAlloc(sizeof(pmSource));
+    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(__func__, 3, "---- %s() end ----\n", __func__);
+    return(tmp);
+}
+
+// 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 = psImageSubset(readout->image, srcRegion);
+    mySource->weight = psImageSubset(readout->weight, srcRegion);
+    mySource->mask   = 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 = psImageSubset(readout->image,  newRegion);
+        mySource->weight = psImageSubset(readout->weight, newRegion);
+        mySource->mask   = 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(__func__, 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;
+            }
+
+            // for the moment, force splane dimensions to be 10x10 image pix
+            binX = tmpSrc->moments->Sx/SCALE;
+            if (binX < 0)
+                continue;
+            if (binX >= splane->numCols)
+                continue;
+
+            binY = tmpSrc->moments->Sy/SCALE;
+            if (binY < 0)
+                continue;
+            if (binY >= splane->numRows)
+                continue;
+
+            splane->data.F32[binY][binX] += 1.0;
+        }
+
+        // find the peak in this image
+        stats = psStatsAlloc (PS_STAT_MAX);
+        stats = psImageStats (stats, splane, NULL, 0);
+        peaks = pmFindImagePeaks (splane, stats[0].max / 2);
+        psTrace (".pmObjects.pmSourceRoughClass", 2, "clump threshold is %f\n", stats[0].max/2);
+
+        psFree (splane);
+        psFree (stats);
+
+    }
+    // XXX EAM : possible errors:
+    //           1) no peak in splane
+    //           2) no significant peak in splane
+
+    // measure statistics on Sx, Sy if Sx, Sy within range of clump
+    {
+        pmPeak *clump;
+        psF32 minSx, maxSx;
+        psF32 minSy, maxSy;
+        psVector *tmpSx = NULL;
+        psVector *tmpSy = NULL;
+        psStats *stats  = NULL;
+
+        // XXX EAM : this lets us takes the single highest peak
+        psArraySort (peaks, 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(__func__, 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(__func__, 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(__func__, 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(__func__, 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(__func__, 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(__func__, 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(__func__, 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/rel11_ifa_pre0/psModules/src/objects/pmSource.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSource.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSource.h	(revision 6961)
@@ -0,0 +1,218 @@
+/** @file  pmSource.h
+ *
+ *  @author EAM, IfA; GLG, MHPCC
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-19 20:29:57 $
+ *
+ *  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
+{
+    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;
+    float extMag;
+    float errMag;
+    float apMag;
+    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();
+
+
+/** 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/rel11_ifa_pre0/psModules/src/objects/pmSourceContour.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceContour.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceContour.c	(revision 6961)
@@ -0,0 +1,451 @@
+/** @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.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-21 21:27:04 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#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(__func__, 4, "---- %s() begin ----\n", __func__);
+
+    // We define variables incr and lastColumn so that we can use the same loop
+    // whether we are stepping leftwards, or rightwards.
+
+    int incr;
+    int subCol;
+    int lastColumn;
+    if (right) {
+        incr = 1;
+        lastColumn = image->numCols - 1;
+    } else {
+        incr = -1;
+        lastColumn = 0;
+    }
+
+    subCol = x;
+
+    while (subCol != lastColumn) {
+        float value = image->data.F32[y][subCol];
+        if (value <= threshold) {
+            psTrace(__func__, 4, "---- %s() end ----\n", __func__);
+            return (subCol);
+        }
+        subCol += incr;
+    }
+    psTrace(__func__, 4, "---- %s() end ----\n", __func__);
+    return (lastColumn);
+}
+
+// return the last coordinate at or below the threshold in the requested direction
+static int findContourPos(
+    psImage *image,
+    float threshold,
+    int x,
+    int y,
+    bool right,
+    int xEnd)
+{
+
+    psTrace(__func__, 4, "---- %s() begin ----\n", __func__);
+
+    // We define variables incr and lastColumn so that we can use the same loop
+    // whether we are stepping leftwards, or rightwards.
+
+    int incr;
+    int subCol;
+    int lastColumn;
+    if (right) {
+        incr = 1;
+        lastColumn = PS_MIN (image->numCols - 1, xEnd);
+    } else {
+        incr = -1;
+        lastColumn = PS_MAX (0, xEnd);
+    }
+
+    subCol = x;
+    while (subCol != lastColumn) {
+        float value = image->data.F32[y][subCol];
+        if (value >= threshold) {
+            psTrace(__func__, 4, "---- %s() end ----\n", __func__);
+            if (subCol == x) {
+                return (subCol);
+            }
+            return (subCol);
+        }
+        subCol += incr;
+    }
+    psTrace(__func__, 4, "---- %s() end ----\n", __func__);
+    return (lastColumn);
+}
+
+/******************************************************************************
+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(__func__, 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(__func__, 4, "---- %s(NAN) end ----\n", __func__);
+        return(NAN);
+    }
+    if (!((0 <= subRow) && (subRow < source->pixels->numRows))) {
+        psTrace(__func__, 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(__func__, 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(__func__, 4, "---- %s() end ----\n", __func__);
+            return((psF32) (subCol + source->pixels->col0));
+        }
+
+        if ((newValue <= level) && (level <= oldValue)) {
+            // This is simple linear interpolation.
+            psTrace(__func__, 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(__func__, 4, "---- %s() end ----\n", __func__);
+            return( ((psF32) (subCol + source->pixels->col0)) + ((psF32) incr) * ((level - oldValue) / (newValue - oldValue)) );
+        }
+
+        subCol+=incr;
+    }
+
+    psTrace(__func__, 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(__func__, 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(image, false);
+
+    int x = xc - image->col0;
+    int y = yc - image->row0;
+
+    // the requested point must be within the contour
+    if (image->data.F32[y][x] < threshold)
+        return NULL;
+
+    // Ensure that the starting column is allowable.
+    if (x < 0)
+        return NULL;
+    if (y < 0)
+        return NULL;
+    if (x >= image->numCols)
+        return NULL;
+    if (y >= image->numRows)
+        return NULL;
+
+    // Allocate data for x/y pairs.
+    psVector *xVec = psVectorAlloc(100, PS_TYPE_F32);
+    psVector *yVec = psVectorAlloc(100, PS_TYPE_F32);
+
+    // First row: find the left and right end-points
+    int Npt = 0;
+
+    x0 = findContourNeg (image, threshold, x, y, LEFT);
+    x1 = findContourNeg (image, threshold, x, y, RIGHT);
+    xVec->data.F32[Npt + 0] = image->col0 + x0;
+    xVec->data.F32[Npt + 1] = image->col0 + x1;
+    yVec->data.F32[Npt + 0] = image->row0 + y;
+    yVec->data.F32[Npt + 1] = image->row0 + y;
+    Npt += 2;
+
+    x0s = x0;
+    x1s = x1;
+
+    // look for contour outline above row
+    xR = x0s;
+    yR = y + 1;
+    while (yR < image->numRows) {
+        if (image->data.F32[yR][xR] < threshold) {
+            x0 = findContourPos (image, threshold, xR, yR, RIGHT, x1);
+            if (x0 == x1) {
+                // fprintf (stderr, "top: %d (%d - %d)\n", yR, xR, x1);
+                goto pt1;
+            }
+            x1 = findContourNeg (image, threshold, x0, yR, RIGHT);
+            x0--;
+        } else {
+            x0 = findContourNeg (image, threshold, xR, yR, LEFT);
+            x1 = findContourNeg (image, threshold, xR, yR, RIGHT);
+        }
+        // fprintf (stderr, "pos: %d (%d - %d)\n", yR, x0, x1);
+
+        xVec->data.F32[Npt + 0] = image->col0 + x0;
+        xVec->data.F32[Npt + 1] = image->col0 + x1;
+        yVec->data.F32[Npt + 0] = image->row0 + yR;
+        yVec->data.F32[Npt + 1] = image->row0 + yR;
+        Npt += 2;
+
+        if (Npt >= xVec->nalloc - 1) {
+            psVectorRealloc (xVec, xVec->nalloc + 100);
+            psVectorRealloc (yVec, yVec->nalloc + 100);
+        }
+        yR ++;
+        xR = x0;
+    }
+
+pt1:
+    // look for contour outline below row
+    xR = x0s;
+    x1 = x1s;
+    yR = y - 1;
+    while (yR >= 0) {
+        if (image->data.F32[yR][xR] < threshold) {
+            x0 = findContourPos (image, threshold, xR, yR, RIGHT, x1);
+            if (x0 == x1) {
+                // fprintf (stderr, "top: %d (%d - %d)\n", yR, xR, x1);
+                goto pt2;
+            }
+            x1 = findContourNeg (image, threshold, x0, yR, RIGHT);
+            x0--;
+        } else {
+            x0 = findContourNeg (image, threshold, xR, yR, LEFT);
+            x1 = findContourNeg (image, threshold, xR, yR, RIGHT);
+        }
+        // fprintf (stderr, "neg: %d (%d - %d)\n", yR, x0, x1);
+
+        xVec->data.F32[Npt + 0] = image->col0 + x0;
+        xVec->data.F32[Npt + 1] = image->col0 + x1;
+        yVec->data.F32[Npt + 0] = image->row0 + yR;
+        yVec->data.F32[Npt + 1] = image->row0 + yR;
+        Npt += 2;
+
+        if (Npt >= xVec->nalloc - 1) {
+            psVectorRealloc (xVec, xVec->nalloc + 100);
+            psVectorRealloc (yVec, yVec->nalloc + 100);
+        }
+        yR --;
+    }
+pt2:
+    xVec->n = Npt;
+    yVec->n = Npt;
+
+    // fprintf (stderr, "done\n");
+    psArray *tmpArray = psArrayAlloc(2);
+    tmpArray->n = 2;
+
+    tmpArray->data[0] = (psPtr *) xVec;
+    tmpArray->data[1] = (psPtr *) yVec;
+    psTrace(__func__, 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(__func__, 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(__func__, 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(__func__, 3, "---- %s(NULL) end ----\n", __func__);
+            return(NULL);
+            //psLogMsg(__func__, PS_LOG_WARN, "WARNING: Could not find contour edge (NAN)\n");
+        }
+        psTrace(__func__, 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(__func__, 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(__func__, 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(__func__, 3, "---- %s() end ----\n", __func__);
+    return(tmpArray);
+}
Index: /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceContour.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceContour.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceContour.h	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/objects/pmSourceFitModel.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceFitModel.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceFitModel.c	(revision 6961)
@@ -0,0 +1,588 @@
+/** @file  pmSourceFitModel.c
+ *
+ *  fit single source models to image pixels
+ *
+ *  @author EAM, IfA
+ *  @author GLG, MHPCC
+ *
+ *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-23 01:07:51 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#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(__func__, 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->moments, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_PTR_NON_NULL(source->mask, false);
+    PS_ASSERT_PTR_NON_NULL(source->weight, false);
+
+    // 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[1] = 0;
+        break;
+    case PM_SOURCE_FIT_PSF:
+        // PSF model only fits x,y,Io
+        nParams = 3;
+        paramMask->data.U8[1] = 0;
+        paramMask->data.U8[2] = 0;
+        paramMask->data.U8[3] = 0;
+        break;
+    case PM_SOURCE_FIT_EXT:
+        // EXT model fits all params (except sky)
+        nParams = params->n - 1;
+        psVectorInit (paramMask, 0);
+        paramMask->data.U8[0] = 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(__func__, 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[2] >= source->pixels->col0);
+    onPic &= (params->data.F32[2] <  source->pixels->col0 + source->pixels->numCols);
+    onPic &= (params->data.F32[3] >= source->pixels->row0);
+    onPic &= (params->data.F32[3] <  source->pixels->row0 + source->pixels->numRows);
+    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(__func__, 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(__func__, 3, "---- %s() begin ----\n", __func__);
+    PS_ASSERT_PTR_NON_NULL(source, false);
+    PS_ASSERT_PTR_NON_NULL(source->moments, false);
+    PS_ASSERT_PTR_NON_NULL(source->peak, false);
+    PS_ASSERT_PTR_NON_NULL(source->pixels, false);
+    PS_ASSERT_PTR_NON_NULL(source->mask, false);
+    PS_ASSERT_PTR_NON_NULL(source->weight, false);
+
+    psBool fitStatus = true;
+    psBool onPic     = true;
+    psBool rc        = true;
+
+    // base values on first model
+    pmModel *model = modelSet->data[0];
+
+    // set the static variables
+    pmModelFitSetInit (model->type);
+
+    int nSrc = modelSet->n;
+    int nPar = model->params->n - 1;  // number of object parameters (excluding sky)
+
+    // define parameter vectors for source set
+    psVector *params = psVectorAlloc (nSrc*nPar + 1, PS_TYPE_F32);
+    psVector *dparams = psVectorAlloc (nSrc*nPar + 1, PS_TYPE_F32);
+    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(__func__, 3, "---- %s() end : fail pixels ----\n", __func__);
+        model->status = PM_MODEL_BADARGS;
+        psFree (x);
+        psFree (y);
+        psFree (yErr);
+        psFree (params);
+        psFree (dparams);
+        psFree(constrain->paramMask);
+        psFree(constrain->paramMin);
+        psFree(constrain->paramMax);
+        psFree(constrain->paramDelta);
+        psFree(constrain);
+        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);
+
+    // 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(__func__, 3, "---- %s end : status %d ----\n", __func__, rc);
+    return(rc);
+}
+
Index: /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceFitModel.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceFitModel.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceFitModel.h	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/objects/pmSourceIO.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO.c	(revision 6961)
@@ -0,0 +1,380 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-23 01:07:51 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include "pslib.h"
+#include "psAdditionals.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, read the table of object data
+bool pmFPAviewWriteObjects (const pmFPAview *view, pmFPAfile *file)
+{
+
+    pmFPA *fpa = file->fpa;
+
+    if (view->chip == -1) {
+        pmFPAWriteObjects (fpa, view, file);
+        return true;
+    }
+
+    if (view->chip >= fpa->chips->n) {
+        return false;
+    }
+    pmChip *chip = fpa->chips->data[view->chip];
+
+    if (view->cell == -1) {
+        pmChipWriteObjects (chip, view, file);
+        return true;
+    }
+
+    if (view->cell >= chip->cells->n) {
+        return false;
+    }
+    pmCell *cell = chip->cells->data[view->cell];
+
+    if (view->readout == -1) {
+        pmCellWriteObjects (cell, view, file);
+        return true;
+    }
+
+    if (view->readout >= cell->readouts->n) {
+        return false;
+    }
+    pmReadout *readout = cell->readouts->data[view->readout];
+
+    pmReadoutWriteObjects (readout, view, file);
+    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];
+        pmChipWriteObjects (chip, view, file);
+    }
+    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];
+        pmCellWriteObjects (cell, view, file);
+    }
+    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];
+        pmReadoutWriteObjects (readout, view, file);
+    }
+    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);
+
+        // check/fix first line (must be SIMPLE = F)
+        psMetadataItem *item = psMetadataGet (outhead, PS_LIST_HEAD);
+        if (strcmp (item->name, "SIMPLE") && strcmp (item->name, "XTENSION")) {
+            psErrorStackPrint(stderr, "invalid header: first line is neither SIMPLE nor XTENSION\n");
+            exit(EXIT_FAILURE);
+        }
+        if (!strcmp (item->name, "XTENSION")) {
+            psMetadataRemoveIndex (outhead, PS_LIST_HEAD);
+        }
+        psMetadataAddBool (outhead, PS_LIST_HEAD, "SIMPLE", PS_META_REPLACE, "CMP file, not simple", false);
+
+        // copy over the entries saved in the
+        updates = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.HEADER");
+        psMetadataCopy (outhead, updates);
+
+        pmSourcesWriteCMP (sources, filename, outhead);
+        psFree (outhead);
+        psFree (filename);
+        break;
+
+    case PM_FPA_FILE_CMF:
+        // write a PHU?
+        // write a header?
+        // write the data
+
+        // get the current header
+        hdu = pmFPAviewThisHDU (view, file->fpa);
+
+        // if file does not yet have a PHU, write it to disk
+        if (file->phu == NULL) {
+            // get the corresponding phu
+            phu = pmFPAviewThisPHU (view, file->fpa);
+
+            // CMF always has extensions
+            outhead = psMetadataCopy (NULL, phu->header);
+            psMetadataAdd (outhead, PS_LIST_TAIL, "EXTEND", PS_DATA_BOOL | PS_META_REPLACE, "this file has extensions", true);
+            psFitsWriteHeaderNotImage (file->fits, outhead);
+            file->phu = phu->header;
+            psTrace ("pmFPAfile", 5, "wrote phu %s (type: %d)\n", file->filename, file->type);
+            psFree (outhead);
+        }
+
+        // this 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);
+            psFitsWriteHeaderNotImage (file->fits, outhead);
+            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);
+        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)
+{
+
+    for (int i = 0; i < cell->readouts->n; i++) {
+
+        pmReadout *readout = cell->readouts->data[i];
+        pmReadoutReadObjects (readout, view, file);
+    }
+    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);
+
+        char *filename = pmFPAfileNameFromRule (file->filerule, file, view);
+        file->fits = psFitsOpen (filename, "r");
+        hdu->header = psFitsReadHeader (NULL, file->fits);
+        psFitsClose (file->fits);
+        sources  = pmSourcesReadCMP (filename, hdu->header);
+        psFree (filename);
+        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;
+    }
+    status = psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSPHOT.SOURCES", PS_DATA_ARRAY, "psphot sources", sources);
+    return true;
+}
+
+
Index: /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO.h	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/objects/pmSourceIO_CMF.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO_CMF.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO_CMF.c	(revision 6961)
@@ -0,0 +1,162 @@
+/** @file  pmSourceIO.c
+ *
+ *  @author EAM, IfA
+ *
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-21 21:27:23 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include "pslib.h"
+#include "psEllipse.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);
+        lsky = (PAR[0] < 1.0) ? 0.0 : log10(PAR[0]);
+
+        shape.sx  = PAR[4];
+        shape.sy  = PAR[5];
+        shape.sxy = PAR[6];
+        axes = psEllipseShapeToAxes (shape);
+
+        row = psMetadataAlloc ();
+        psMetadataAdd (row, PS_LIST_TAIL, "X_PIX",   PS_DATA_F32, "", PAR[2]);
+        psMetadataAdd (row, PS_LIST_TAIL, "Y_PIX",   PS_DATA_F32, "", PAR[3]);
+        psMetadataAdd (row, PS_LIST_TAIL, "MAG_RAW", PS_DATA_F32, "", 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)));
+        psMetadataAdd (row, PS_LIST_TAIL, "DUMMY",   PS_DATA_STRING, "", "12");
+
+        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
+    psFitsWriteTable (fits, header, table);
+    psTrace ("pmFPAfile", 5, "wrote ext data %s\n", extname);
+
+    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[0]         = pow(lsky, 10.0);
+        PAR[2]         = psMetadataLookupF32 (&status, row, "X_PIX");
+        PAR[3]         = 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[4] = shape.sx;
+        PAR[5] = shape.sy;
+        PAR[6] = 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/rel11_ifa_pre0/psModules/src/objects/pmSourceIO_CMP.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO_CMP.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO_CMP.c	(revision 6961)
@@ -0,0 +1,250 @@
+/** @file  pmSourceIO.c
+ *
+ *  @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
+ *
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include "pslib.h"
+#include "psLine.h"
+#include "psEllipse.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 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);
+
+    // set NAXIS to 0 : CFITSIO requires isolated header to have NAXIS = 0
+    mdi = psMetadataLookup (header, "NAXIS");
+    mdi->data.S32 = 0;
+    mdi->type = PS_DATA_S32;
+
+    // create file, write-out header
+    psFits *fits = psFitsOpen (filename, "w");
+    psFitsWriteHeader (header, fits);
+    psFitsClose (fits);
+
+    // re-open, add data to end of file
+    FILE *f = fopen (filename, "a+");
+    if (f == NULL) {
+        psLogMsg ("WriteSourceOBJ", 3, "can't open output file for output %s\n", filename);
+        return false;
+    }
+    fseek (f, 0, SEEK_END);
+
+    psLine *line = psLineAlloc (67);  // 66 is imclean-defined line length
+
+    // write sources with models first
+    for (i = 0; i < sources->n; i++) {
+        pmSource *source = (pmSource *) sources->data[i];
+        pmModel *model = pmSourceSelectModel (source);
+        if (model == NULL)
+            continue;
+
+        PAR = model->params->data.F32;
+        dPAR = model->dparams->data.F32;
+
+        type = pmSourceDophotType (source);
+        lsky = (PAR[0] < 1.0) ? 0.0 : log10(PAR[0]);
+
+        shape.sx  = PAR[4];
+        shape.sy  = PAR[5];
+        shape.sxy = PAR[6];
+        axes = psEllipseShapeToAxes (shape);
+
+        psLineInit (line);
+        psLineAdd (line, "%6.1f ",  PAR[2]);
+        psLineAdd (line, "%6.1f ",  PAR[3]);
+        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)
+{
+
+    int Ninstar;
+    psF32 *PAR, *dPAR;
+    bool status;
+    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?
+    int nLines = header->list->n;
+    int nBytes = nLines * 80;
+    if (nBytes % 2880) {
+        int nBlock = 1 + (int)(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;
+    }
+    fseek (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
+    char *buffer = psAlloc (BYTES_STAR*nStars);
+
+    int Nextra = 0;
+    while (true) {
+        /* load next data block */
+        int Nbytes = BYTES_STAR * BLOCK - Nextra;
+        int 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, " ");
+            psArray *array = psListToArray (list);
+
+            pmSource *source = pmSourceAlloc ();
+            pmModel *model = pmModelAlloc (modelType);
+            source->modelPSF  = model;
+
+            PAR = model->params->data.F32;
+            dPAR = model->dparams->data.F32;
+
+            PAR[0]         = pow (atof (array->data[5]), 10.0);
+            PAR[2]         = atof (array->data[0]);
+            PAR[3]         = 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->type   = atof (array->data[4]);
+
+            psArrayAdd (sources, 100, source);
+        }
+    }
+done_load:
+
+    // XXX free up temp memory in this block
+    // XXX if sources->n != nStars, give an error?
+
+    fclose (f);
+    return (sources);
+}
Index: /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO_OBJ.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO_OBJ.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO_OBJ.c	(revision 6961)
@@ -0,0 +1,94 @@
+/** @file  pmSourceIO.c
+ *
+ *  @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
+ *
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include "pslib.h"
+#include "psLine.h"
+#include "psEllipse.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[1] / PAR[1];
+        type = pmSourceDophotType (source);
+        if ((source->apMag < 99.0) && (source->psfMag < 99.0)) {
+            apResid = source->apMag - source->psfMag;
+        } else {
+            apResid = 0.0;
+        }
+
+        shape.sx  = PAR[4];
+        shape.sy  = PAR[5];
+        shape.sxy = PAR[6];
+        axes = psEllipseShapeToAxes (shape);
+
+        psLineInit (line);
+        psLineAdd (line, "%3d",   type);
+        psLineAdd (line, "%8.2f", PAR[2]);
+        psLineAdd (line, "%8.2f", PAR[3]);
+        psLineAdd (line, "%8.3f", source->psfMag);
+        psLineAdd (line, "%6.3f", dmag);
+        psLineAdd (line, "%9.2f", PAR[0]);
+        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/rel11_ifa_pre0/psModules/src/objects/pmSourceIO_RAW.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO_RAW.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO_RAW.c	(revision 6961)
@@ -0,0 +1,268 @@
+/** @file  pmSourceIO.c
+ *
+ *  @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
+ *
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include "pslib.h"
+#include "psLine.h"
+#include "psEllipse.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[2], dPAR[3]);
+
+        fprintf (f, "%7.1f %7.1f  %7.1f %8.4f  %7.4f %7.4f  ",
+                 PAR[2], PAR[3], PAR[0], 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[4] / PAR[4]), (dPAR[5] / PAR[5]));
+
+        fprintf (f, "%7.1f %7.1f  %7.1f %8.4f  %7.4f %7.4f  ",
+                 PAR[2], PAR[3], PAR[0], 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,
+                 source->moments->x, source->moments->y,
+                 source->moments->Sx, source->moments->Sy,
+                 source->moments->Sum, source->moments->Peak,
+                 source->moments->Sky, source->moments->SN,
+                 source->moments->nPixels, source->type);
+    }
+    fclose (f);
+    psFree (empty);
+    return true;
+}
+
+// write the moments to an output file
+bool pmMomentsWriteText (psArray *sources, char *filename)
+{
+
+    int i;
+    FILE *f;
+    pmSource *source = NULL;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg ("pmMomentsWriteText", 3, "can't open output file for moments%s\n", filename);
+        return false;
+    }
+
+    for (i = 0; i < sources->n; i++) {
+        source = sources->data[i];
+        if (source->moments == NULL)
+            continue;
+        fprintf (f, "%5d %5d  %7.1f  %7.1f %7.1f  %6.3f %6.3f  %10.1f %7.1f %7.1f %7.1f  %4d %2d %#5x\n",
+                 source->peak->x, source->peak->y, source->peak->counts,
+                 source->moments->x, source->moments->y,
+                 source->moments->Sx, source->moments->Sy,
+                 source->moments->Sum, source->moments->Peak,
+                 source->moments->Sky, source->moments->SN,
+                 source->moments->nPixels, source->type, source->mode);
+    }
+    fclose (f);
+    return true;
+}
+
+// write the peaks to an output file
+bool pmPeaksWriteText (psArray *peaks, char *filename)
+{
+
+    int i;
+    FILE *f;
+
+    f = fopen (filename, "w");
+    if (f == NULL) {
+        psLogMsg ("pmPeaksWriteText", 3, "can't open output file for peaks%s\n", filename);
+        return false;
+    }
+
+    for (i = 0; i < peaks->n; i++) {
+        pmPeak *peak = peaks->data[i];
+        if (peak == NULL)
+            continue;
+        fprintf (f, "%5d %5d  %7.1f\n",
+                 peak->x, peak->y, peak->counts);
+    }
+    fclose (f);
+    return true;
+}
+
Index: /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO_SX.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO_SX.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceIO_SX.c	(revision 6961)
@@ -0,0 +1,86 @@
+/** @file  pmSourceIO.c
+ *
+ *  @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
+ *
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include "pslib.h"
+#include "psLine.h"
+#include "psEllipse.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[4];
+        shape.sy  = PAR[5];
+        shape.sxy = PAR[6];
+        axes = psEllipseShapeToAxes (shape);
+
+        psLineInit (line);
+        psLineAdd (line, "%5.2f",  0.0); // should be type
+        psLineAdd (line, "%11.3f", PAR[2]);
+        psLineAdd (line, "%11.3f", PAR[3]);
+        psLineAdd (line, "%9.4f",  source->psfMag);
+        psLineAdd (line, "%9.4f",  source->errMag);
+        psLineAdd (line, "%13.4f", PAR[0]);
+        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/rel11_ifa_pre0/psModules/src/objects/pmSourcePhotometry.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSourcePhotometry.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSourcePhotometry.c	(revision 6961)
@@ -0,0 +1,392 @@
+/** @file  pmSourcePhotometry.c
+ *
+ *  @author EAM, IfA; GLG, MHPCC
+ *
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-23 01:07:51 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include "pslib.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmMaskBadPixels.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[1] > 0) {
+        SN = model->params->data.F32[1] / model->dparams->data.F32[1];
+        source->errMag = 1.0 / SN;
+    } else {
+        SN = 0.0;
+        source->errMag = 0.0;
+    }
+    x = model->params->data.F32[2];
+    y = model->params->data.F32[3];
+
+    // 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[0];
+    } 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[0];
+    } 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[2];
+    Yo = params->data.F32[3];
+
+    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);
+}
+
+float pmSourceCrossProduct (pmSource *Mi, pmSource *Mj)
+{
+
+    int Xs, Xe, Ys, Ye;
+    int xi, xj, yi, yj;
+    int xIs, xJs, yIs, yJs;
+    int xIe, yIe;
+    float flux, wt;
+
+    psImage *Pi = Mi->pixels;
+    psImage *Pj = Mj->pixels;
+
+    psImage *Wi = Mi->weight;
+
+    psImage *Ti = Mi->mask;
+    psImage *Tj = Mj->mask;
+
+    Xs = PS_MAX (Pi->col0, Pj->col0);
+    Xe = PS_MIN (Pi->col0 + Pi->numCols, Pj->col0 + Pj->numCols);
+
+    Ys = PS_MAX (Pi->row0, Pj->row0);
+    Ye = PS_MIN (Pi->row0 + Pi->numRows, Pj->row0 + Pj->numRows);
+
+    xIs = Xs - Pi->col0;
+    xJs = Xs - Pj->col0;
+    yIs = Ys - Pi->row0;
+    yJs = Ys - Pj->row0;
+
+    xIe = Xe - Pi->col0;
+    yIe = Ye - Pi->row0;
+
+    // note that this is addressing the same image pixels,
+    // though only if both are source not model images
+    flux = 0;
+    for (yi = yIs, yj = yJs; yi < yIe; yi++, yj++) {
+        for (xi = xIs, xj = xJs; xi < xIe; xi++, xj++) {
+            if (Ti->data.U8[yi][xi])
+                continue;
+            if (Tj->data.U8[yj][xj])
+                continue;
+            wt = Wi->data.F32[yi][xi];
+            if (wt > 0) {
+                flux += (Pi->data.F32[yi][xi] * Pj->data.F32[yj][xj]) / wt;
+            }
+        }
+    }
+    return (flux);
+}
+
+float pmSourceCrossWeight (pmSource *Mi, pmSource *Mj)
+{
+
+    int Xs, Xe, Ys, Ye;
+    int xi, xj, yi, yj;
+    int xIs, xJs, yIs, yJs;
+    int xIe, yIe;
+    float flux, wt;
+
+    psImage *Pi = Mi->pixels;
+    psImage *Pj = Mj->pixels;
+
+    psImage *Wi = Mi->weight;
+
+    psImage *Ti = Mi->mask;
+    psImage *Tj = Mj->mask;
+
+    Xs = PS_MAX (Pi->col0, Pj->col0);
+    Xe = PS_MIN (Pi->col0 + Pi->numCols, Pj->col0 + Pj->numCols);
+
+    Ys = PS_MAX (Pi->row0, Pj->row0);
+    Ye = PS_MIN (Pi->row0 + Pi->numRows, Pj->row0 + Pj->numRows);
+
+    xIs = Xs - Pi->col0;
+    xJs = Xs - Pj->col0;
+    yIs = Ys - Pi->row0;
+    yJs = Ys - Pj->row0;
+
+    xIe = Xe - Pi->col0;
+    yIe = Ye - Pi->row0;
+
+    // note that this is addressing the same image pixels,
+    // though only if both are source not model images
+    flux = 0;
+    for (yi = yIs, yj = yJs; yi < yIe; yi++, yj++) {
+        for (xi = xIs, xj = xJs; xi < xIe; xi++, xj++) {
+            if (Ti->data.U8[yi][xi])
+                continue;
+            if (Tj->data.U8[yj][xj])
+                continue;
+            wt = Wi->data.F32[yi][xi];
+            if (wt > 0) {
+                flux += 1.0 / wt;
+            }
+        }
+    }
+    return (flux);
+}
+
Index: /tags/rel11_ifa_pre0/psModules/src/objects/pmSourcePhotometry.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSourcePhotometry.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSourcePhotometry.h	(revision 6961)
@@ -0,0 +1,53 @@
+/** @file  pmSourcePhotometry.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_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);
+float pmSourceCrossProduct (pmSource *Mi, pmSource *Mj);
+float pmSourceCrossWeight (pmSource *Mi, pmSource *Mj);
+bool pmSourcePixelWeight (float *pixWeight, pmModel *model, psImage *image, psImage *mask);
+
+# endif /* PM_SOURCE_PHOTOMETRY_H */
Index: /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceSky.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceSky.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceSky.c	(revision 6961)
@@ -0,0 +1,121 @@
+/** @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.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-04-19 20:37:35 $
+ *
+ *  Copyright 2004 Maui High Performance Computing Center, University of Hawaii
+ *
+ */
+
+#include <stdio.h>
+#include <math.h>
+#include <string.h>
+#include "pslib.h"
+#include "pmHDU.h"
+#include "pmFPA.h"
+#include "pmMaskBadPixels.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(__func__, 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);
+
+    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));
+
+    psF64 tmpF64;
+    p_psGetStatValue(myStats, &tmpF64);
+    psFree(myStats);
+
+    if (isnan(tmpF64)) {
+        psTrace(__func__, 3, "---- %s(false) end ----\n", __func__);
+        return(false);
+    }
+    if (source->moments == NULL) {
+        source->moments = pmMomentsAlloc();
+    }
+    source->moments->Sky = (psF32) tmpF64;
+    psTrace(__func__, 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(__func__, 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);
+
+    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));
+
+    psF64 tmpF64;
+    p_psGetStatValue(myStats, &tmpF64);
+    psFree(myStats);
+
+    if (isnan(tmpF64)) {
+        psTrace(__func__, 3, "---- %s(false) end ----\n", __func__);
+        return(false);
+    }
+    if (source->moments == NULL) {
+        source->moments = pmMomentsAlloc();
+    }
+    source->moments->dSky = (psF32) tmpF64;
+    psTrace(__func__, 3, "---- %s(true) end ----\n", __func__);
+    return (true);
+}
Index: /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceSky.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceSky.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/objects/pmSourceSky.h	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/parseErrorCodes.pl
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/parseErrorCodes.pl	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/parseErrorCodes.pl	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/photom/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/photom/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/photom/.cvsignore	(revision 6961)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/rel11_ifa_pre0/psModules/src/photom/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/photom/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/photom/Makefile.am	(revision 6961)
@@ -0,0 +1,9 @@
+noinst_LTLIBRARIES = libpsmodulephotom.la
+
+libpsmodulephotom_la_CPPFLAGS = $(SRCINC) $(PSMODULE_CFLAGS)
+libpsmodulephotom_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulephotom_la_SOURCES  =
+
+psmoduleincludedir = $(includedir)
+psmoduleinclude_HEADERS =
+
Index: /tags/rel11_ifa_pre0/psModules/src/psErrorCodes.dat
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/psErrorCodes.dat	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/psErrorCodes.dat	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/src/pslib/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/.cvsignore	(revision 6961)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+*.la
+*.lo
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/Makefile.am	(revision 6961)
@@ -0,0 +1,30 @@
+noinst_LTLIBRARIES = libpsmodulepslib.la
+
+libpsmodulepslib_la_CPPFLAGS = $(SRCINC) $(PSMODULE_CFLAGS)
+libpsmodulepslib_la_LDFLAGS  = -release $(PACKAGE_VERSION)
+libpsmodulepslib_la_SOURCES  = \
+    psAdditionals.c \
+    psEllipse.c \
+    psImageJpeg.c \
+    psImageFlip.c \
+    psImageUnbin.c \
+    psLine.c \
+    psMetadataItemCompare.c \
+    psMetadataItemParse.c \
+    psPolynomialUtils.c \
+    psRegionIsBad.c \
+    psSparse.c
+
+psmoduleincludedir = $(includedir)
+psmoduleinclude_HEADERS = \
+    psAdditionals.h \
+    psEllipse.h \
+    psImageJpeg.h \
+    psImageFlip.h \
+    psImageUnbin.h \
+    psLine.h \
+    psMetadataItemCompare.h \
+    psMetadataItemParse.h \
+    psPolynomialUtils.h \
+    psRegionIsBad.h \
+    psSparse.h
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psAdditionals.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psAdditionals.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psAdditionals.c	(revision 6961)
@@ -0,0 +1,75 @@
+#include <stdio.h>
+#include <strings.h>
+#include "pslib.h"
+#include "psAdditionals.h"
+
+// may need to extend this to change the keyname in the copy
+bool psMetadataItemTransfer (psMetadata *out, psMetadata *in, char *key)
+{
+
+    psMetadataItem *item = psMetadataLookup (in, key);
+    if (item == NULL)
+        return false;
+
+    psMetadataAddItem (out, item, PS_LIST_TAIL, PS_META_REPLACE);
+    return true;
+}
+
+psArray *psStringSplitArray (const char *string, const char *splitters)
+{
+
+    psList *list = psStringSplit (string, splitters);
+    psArray *array = psListToArray (list);
+    psFree (list);
+    return array;
+}
+
+#ifndef whitespace
+#define whitespace(c) (((c) == ' ') || ((c) == '\t'))
+#endif
+
+/* Strip whitespace from the start and end of STRING. */
+int psStringStrip (char *string)
+{
+
+    int i;
+
+    if (string == (char *) NULL)
+        return (FALSE);
+
+    for (i = 0; whitespace (string[i]); i++)
+        ;
+    if (i)
+        memmove (string, string + i, strlen(string+i)+1);
+    for (i = strlen (string) - 1; (i > 0) && whitespace (string[i]); i--)
+        ;
+    string[++i] = 0;
+    return (i);
+
+}
+
+bool psFitsWriteHeaderNotImage (psFits *fits, psMetadata *header)
+{
+
+    int status = 0;
+    char fitsErr[1024];
+
+    psMetadataItem *mdi = NULL;
+    mdi = psMetadataLookup (header, "NAXIS");
+    if (mdi != NULL) {
+        mdi->data.S32 = 0;
+        mdi->type = PS_DATA_S32;
+    }
+
+    fits_create_img (fits->fd, SHORT_IMG, 0, NULL, &status);
+
+    if (status != 0) {
+        fits_get_errstatus (status, fitsErr);
+        psError (PS_ERR_IO, true, PS_ERRORTEXT_psFits_WRITE_FAILED, fitsErr);
+        exit (2);
+    }
+
+    psFitsWriteHeader (header, fits);
+
+    return true;
+}
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psAdditionals.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psAdditionals.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psAdditionals.h	(revision 6961)
@@ -0,0 +1,25 @@
+// Functions that should go into psLib.
+
+
+#ifndef PS_ADDITIONALS_H
+#define PS_ADDITIONALS_H
+
+#include "pslib.h"
+
+// copy a metadata item from one psMetadata to another
+bool psMetadataItemTransfer (psMetadata *out, // Destination: copy is placed here
+                             psMetadata *in,  // Source: item comes from here
+                             char *key        // key to identify the metadata item
+                            );
+
+// Split string on given characters
+psArray *psStringSplitArray(const char *string, // String to split
+                            const char *splitters // Characters on which to split
+                           );
+
+// strip whitespace from head and tail of string
+int psStringStrip (char *string);
+
+// write out header with NAXIS=0
+bool psFitsWriteHeaderNotImage (psFits *fits, psMetadata *header);
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psEllipse.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psEllipse.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psEllipse.c	(revision 6961)
@@ -0,0 +1,62 @@
+# include "pslib.h"
+# include "psEllipse.h"
+
+psEllipseAxes psEllipseMomentsToAxes (psEllipseMoments moments)
+{
+
+    psEllipseAxes axes;
+
+    double f = sqrt (0.25*PS_SQR(moments.x2 - moments.y2) + PS_SQR(moments.xy));
+    if (f > (moments.x2 + moments.y2) / 2.0) {
+        f = 0.98*(moments.x2 + moments.y2) / 2.0;
+    }
+
+    axes.major = sqrt (0.5*(moments.x2 + moments.y2) + f);
+    axes.minor = sqrt (0.5*(moments.x2 + moments.y2) - f);
+    axes.theta = atan2 (2*moments.xy, moments.x2 - moments.y2) / 2;
+    // theta in radians
+
+    return (axes);
+}
+
+psEllipseShape psEllipseAxesToShape (psEllipseAxes axes)
+{
+
+    psEllipseShape shape;
+
+    double r1 = 1.0 / PS_SQR(axes.major) + 1.0 / PS_SQR(axes.minor);
+    double r2 = 1.0 / PS_SQR(axes.major) - 1.0 / PS_SQR(axes.minor);
+
+    double sxr = r1 + r2*cos(2*axes.theta);
+    double syr = r1 - r2*cos(2*axes.theta);
+
+    shape.sx = 1.0 / sqrt(sxr);
+    shape.sy = 1.0 / sqrt(syr);
+    shape.sxy = r2*sin(2*axes.theta);
+
+    return (shape);
+}
+
+psEllipseAxes psEllipseShapeToAxes (psEllipseShape shape)
+{
+
+    psEllipseAxes axes;
+
+    double f1 = 1.0 / PS_SQR(shape.sx) + 1.0 / PS_SQR(shape.sy);
+    double f2 = 1.0 / PS_SQR(shape.sx) - 1.0 / PS_SQR(shape.sy);
+
+    // force the axis ratio to be less than 10
+    double r1 = 0.5*0.95*sqrt (PS_SQR(f1) - PS_SQR(f2));
+
+    shape.sxy = PS_MIN(PS_MAX(shape.sxy, -r1), r1);
+
+    axes.theta = atan2 (-2.0*shape.sxy, f2) / 2.0;
+
+    double Ar = 0.25*f1 + 0.25*sqrt(PS_SQR(f2) + 4*PS_SQR(shape.sxy));
+    double Br = 0.25*f1 - 0.25*sqrt(PS_SQR(f2) + 4*PS_SQR(shape.sxy));
+
+    axes.minor = 1.0 / sqrt (Ar);
+    axes.major = 1.0 / sqrt (Br);
+
+    return (axes);
+}
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psEllipse.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psEllipse.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psEllipse.h	(revision 6961)
@@ -0,0 +1,30 @@
+// strucures to define elliptical shape parameters
+typedef struct
+{
+    double major;
+    double minor;
+    double theta;
+}
+psEllipseAxes;
+
+typedef struct
+{
+    double x2;
+    double y2;
+    double xy;
+}
+psEllipseMoments;
+
+typedef struct
+{
+    double sx;
+    double sy;
+    double sxy;
+}
+psEllipseShape;
+
+// conversions between elliptical shape representations
+psEllipseAxes psEllipseMomentsToAxes (psEllipseMoments moments);
+psEllipseShape psEllipseAxesToShape (psEllipseAxes axes);
+psEllipseAxes psEllipseShapeToAxes (psEllipseShape shape);
+
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psImageFlip.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psImageFlip.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psImageFlip.c	(revision 6961)
@@ -0,0 +1,76 @@
+#include <stdio.h>
+#include "pslib.h"
+
+
+#define FLIP_X_CASE(TYPENAME,TYPE) \
+case TYPENAME: \
+for (int i = 0; i < image->numRows; i++) { \
+    for (int j = 0; j < image->numCols; j++) { \
+        output->data.TYPE[i][j] = image->data.TYPE[i][image->numCols - j - 1]; \
+    } \
+} \
+break;
+
+// Return a flipped image in x
+psImage *psImageFlipX(psImage *image    // Image to flip
+                     )
+{
+    psImage *output = psImageAlloc(image->numCols, image->numRows, image->type.type); // Output image
+    switch(image->type.type) {
+        FLIP_X_CASE(PS_TYPE_U8,  U8);
+        FLIP_X_CASE(PS_TYPE_U16, U16);
+        FLIP_X_CASE(PS_TYPE_U32, U32);
+        FLIP_X_CASE(PS_TYPE_U64, U64);
+        FLIP_X_CASE(PS_TYPE_S8,  S8);
+        FLIP_X_CASE(PS_TYPE_S16, S16);
+        FLIP_X_CASE(PS_TYPE_S32, S32);
+        FLIP_X_CASE(PS_TYPE_S64, S64);
+        FLIP_X_CASE(PS_TYPE_F32, F32);
+        FLIP_X_CASE(PS_TYPE_F64, F64);
+        FLIP_X_CASE(PS_TYPE_C32, C32);
+        FLIP_X_CASE(PS_TYPE_C64, C64);
+    default:
+        psFree(output);
+        psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Unknown type for input image: %x\n", image->type.type);
+        return NULL;
+    }
+
+    return output;
+}
+
+
+#define FLIP_Y_CASE(TYPENAME,TYPE) \
+case TYPENAME: \
+for (int i = 0; i < image->numRows; i++) { \
+    for (int j = 0; j < image->numCols; j++) { \
+        output->data.TYPE[i][j] = image->data.TYPE[image->numRows - i - 1][j]; \
+    } \
+} \
+break;
+
+// Return a flipped image in y
+psImage *psImageFlipY(psImage *image    // Image to flip
+                     )
+{
+    psImage *output = psImageAlloc(image->numCols, image->numRows, image->type.type); // Output image
+    switch(image->type.type) {
+        FLIP_Y_CASE(PS_TYPE_U8,  U8);
+        FLIP_Y_CASE(PS_TYPE_U16, U16);
+        FLIP_Y_CASE(PS_TYPE_U32, U32);
+        FLIP_Y_CASE(PS_TYPE_U64, U64);
+        FLIP_Y_CASE(PS_TYPE_S8,  S8);
+        FLIP_Y_CASE(PS_TYPE_S16, S16);
+        FLIP_Y_CASE(PS_TYPE_S32, S32);
+        FLIP_Y_CASE(PS_TYPE_S64, S64);
+        FLIP_Y_CASE(PS_TYPE_F32, F32);
+        FLIP_Y_CASE(PS_TYPE_F64, F64);
+        FLIP_Y_CASE(PS_TYPE_C32, C32);
+        FLIP_Y_CASE(PS_TYPE_C64, C64);
+    default:
+        psFree(output);
+        psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Unknown type for input image: %x\n", image->type.type);
+        return NULL;
+    }
+
+    return output;
+}
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psImageFlip.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psImageFlip.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psImageFlip.h	(revision 6961)
@@ -0,0 +1,13 @@
+#ifndef PS_IMAGE_FLIP_H
+#define PS_IMAGE_FLIP_H
+
+#include "pslib.h"
+
+// Return a flipped image in x
+psImage *psImageFlipX(psImage *image    // Image to flip
+                     );
+// Return a flipped image in y
+psImage *psImageFlipY(psImage *image    // Image to flip
+                     );
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psImageJpeg.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psImageJpeg.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psImageJpeg.c	(revision 6961)
@@ -0,0 +1,191 @@
+# include <stdio.h>
+# include <strings.h>  // for strcasecmp
+# include <unistd.h>   // for unlink
+# include <pslib.h>
+# include "jpeglib.h"
+# include "psImageJpeg.h"
+
+static void psImageJpegColormapFree (psImageJpegColormap *map)
+{
+
+    if (map == NULL)
+        return;
+
+    psFree (map->red);
+    psFree (map->green);
+    psFree (map->blue);
+    return;
+}
+
+psImageJpegColormap *psImageJpegColormapAlloc ()
+{
+
+    psImageJpegColormap *map;
+    map = psAlloc (sizeof(psImageJpegColormap));
+    psMemSetDeallocator(map, (psFreeFunc) psImageJpegColormapFree);
+
+    map->red   = psVectorAlloc (256, PS_TYPE_U8);
+    map->blue  = psVectorAlloc (256, PS_TYPE_U8);
+    map->green = psVectorAlloc (256, PS_TYPE_U8);
+
+    return (map);
+}
+
+psImageJpegColormap *psImageJpegColormapSet (psImageJpegColormap *map, char *name)
+{
+
+    if (map == NULL) {
+        map = psImageJpegColormapAlloc ();
+    }
+
+    /* grayscale */
+    if ((!strcasecmp (name, "grayscale")) || (!strcasecmp (name, "greyscale"))) {
+        for (int i = 0; i < map->red->n; i++) {
+            map->red->data.U8[i]   = PS_JPEG_RANGELIM(i);
+            map->green->data.U8[i] = PS_JPEG_RANGELIM(i);
+            map->blue->data.U8[i]  = PS_JPEG_RANGELIM(i);
+        }
+        return map;
+    }
+
+    /* -grayscale */
+    if ((!strcasecmp (name, "-grayscale")) || (!strcasecmp (name, "-greyscale"))) {
+        for (int i = 0; i < map->red->n; i++) {
+            map->red->data.U8[i]   = PS_JPEG_RANGELIM(256 - i);
+            map->green->data.U8[i] = PS_JPEG_RANGELIM(256 - i);
+            map->blue->data.U8[i]  = PS_JPEG_RANGELIM(256 - i);
+        }
+        return map;
+    }
+
+    /* rainbow */
+    if (!strcasecmp (name, "rainbow")) {
+        int I1 = 0.25*map->red->n;
+        int I2 = 0.50*map->red->n;
+        int I3 = 0.75*map->red->n;
+        for (int i = 0; i < I1; i++) {
+            map->red->data.U8[i]   = 0;
+            map->green->data.U8[i] = 0;
+            map->blue->data.U8[i]  = PS_JPEG_RANGELIM(4*i);
+        }
+        for (int i = I1; i < I2; i++) {
+            map->red->data.U8[i]   = PS_JPEG_RANGELIM(4*(i - I1));
+            map->green->data.U8[i] = 0;
+            map->blue->data.U8[i]  = PS_JPEG_RANGELIM(4*(I2 - i));
+        }
+        for (int i = I2; i < I3; i++) {
+            map->red->data.U8[i]   = 255;
+            map->green->data.U8[i] = 4*(i - I2);
+            map->blue->data.U8[i]  = 0;
+        }
+        for (int i = I3; i < map->red->n; i++) {
+            map->red->data.U8[i]   = 255;
+            map->green->data.U8[i] = 255;
+            map->blue->data.U8[i]  = PS_JPEG_RANGELIM(4*(i - I3));
+        }
+        return map;
+    }
+
+    /* heat */
+    if (!strcasecmp (name, "heat")) {
+        int I1 = 0.25*map->red->n;
+        int I2 = 0.50*map->red->n;
+        int I3 = 0.75*map->red->n;
+        for (int i = 0; i < I1; i++) {
+            map->red->data.U8[i]   = PS_JPEG_RANGELIM(2*i);
+            map->green->data.U8[i] = 0;
+            map->blue->data.U8[i]  = 0;
+        }
+        for (int i = I1; i < I2; i++) {
+            map->red->data.U8[i]   = PS_JPEG_RANGELIM(2*i);
+            map->green->data.U8[i] = PS_JPEG_RANGELIM(2*(i - I1));
+            map->blue->data.U8[i]  = 0;
+        }
+        for (int i = I2; i < I3; i++) {
+            map->red->data.U8[i]   = 255;
+            map->green->data.U8[i] = PS_JPEG_RANGELIM(2*(i - I1));
+            map->blue->data.U8[i]  = PS_JPEG_RANGELIM(2*(i - I2));
+        }
+        for (int i = I3; i < map->red->n; i++) {
+            map->red->data.U8[i]   = 255;
+            map->green->data.U8[i] = 255;
+            map->blue->data.U8[i]  = PS_JPEG_RANGELIM(2*(i - I2));
+        }
+        return map;
+    }
+
+    // invalid colormap : warn user
+    map = psImageJpegColormapSet (map, "greyscale");
+    return map;
+}
+
+bool psImageJpeg (psImageJpegColormap *map, psImage *image, char *filename, float min, float max)
+{
+
+    struct jpeg_compress_struct cinfo;
+    struct jpeg_error_mgr jerr;
+
+    int pixel;
+    JSAMPLE *jpegLine;   // Points to data for current line
+    JSAMPROW jpegLineList[1];  // pointer to JSAMPLE row[s]
+    JSAMPLE *outPix;
+
+    if (map->red == NULL)
+        return false;
+    if (map->green == NULL)
+        return false;
+    if (map->blue == NULL)
+        return false;
+
+    /* JPEG init calls */
+    cinfo.err = jpeg_std_error (&jerr);
+    jpeg_create_compress (&cinfo);
+
+    /* open file, prep for jpeg */
+    FILE *f = fopen (filename, "w");
+    if (f == NULL) {
+        fprintf (stderr, "failed to open %s for output\n", filename);
+        return (TRUE);
+    }
+    jpeg_stdio_dest(&cinfo, f);
+
+    /* set up color jpeg buffers */
+    int quality = 75;
+    cinfo.image_width = image->numCols; // image width and height, in pixels
+    cinfo.image_height = image->numRows;
+    cinfo.input_components = 3;
+    cinfo.in_color_space = JCS_RGB;
+    jpeg_set_defaults (&cinfo);
+    jpeg_set_quality (&cinfo, quality, true); // limit to baseline-JPEG values
+    jpeg_start_compress (&cinfo, true);
+
+    jpegLine = psAlloc (3*image->numCols*sizeof(JSAMPLE));
+    jpegLineList[0] = jpegLine;
+
+    psU8 *Rpix = map->red->data.U8;
+    psU8 *Gpix = map->green->data.U8;
+    psU8 *Bpix = map->blue->data.U8;
+
+    float zero = min;
+    float scale = 256.0/(max - min);
+
+    for (int j = 0; j < image->numRows; j++) {
+        psF32 *row = image->data.F32[j];
+
+        outPix = jpegLine;
+        for (int i = 0; i < image->numCols; i++, outPix += 3) {
+            pixel = PS_JPEG_SCALEVALUE(row[i],zero,scale);
+            outPix[0] = Rpix[pixel];
+            outPix[1] = Gpix[pixel];
+            outPix[2] = Bpix[pixel];
+        }
+        jpeg_write_scanlines (&cinfo, jpegLineList, 1);
+    }
+
+    jpeg_finish_compress (&cinfo);
+    fclose (f);
+    jpeg_destroy_compress (&cinfo);
+
+    psFree (jpegLine);
+    return true;
+}
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psImageJpeg.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psImageJpeg.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psImageJpeg.h	(revision 6961)
@@ -0,0 +1,32 @@
+/** @file  psImageJpeg.h
+ *
+ * the functions to generate JPEG images from psImage 
+ */
+
+# ifndef PS_IMAGE_JPEG_H
+# define PS_IMAGE_JPEG_H
+
+typedef struct
+{
+    psString name;
+    psVector *red;
+    psVector *green;
+    psVector *blue;
+}
+psImageJpegColormap;
+
+# define PS_JPEG_RANGELIM(A)(PS_MAX(0,PS_MIN(255,(A))))
+
+# define PS_JPEG_SCALEVALUE(VALUE,ZERO,SCALE)(PS_MAX(0,PS_MIN(255,(SCALE*(VALUE-ZERO)))))
+
+// allocate a colormap (does not define the map values)
+psImageJpegColormap *psImageJpegColormapAlloc ();
+
+// set the colormap values using the supplied name
+psImageJpegColormap *psImageJpegColormapSet (psImageJpegColormap *map, char *name);
+
+// write out a JPEG file using the supplied image and colormap
+// output goes to the specified filename
+bool psImageJpeg (psImageJpegColormap *map, psImage *image, char *filename, float min, float max);
+
+# endif /* PS_IMAGE_JPEG_H */
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psImageUnbin.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psImageUnbin.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psImageUnbin.c	(revision 6961)
@@ -0,0 +1,273 @@
+#include <stdio.h>
+#include "pslib.h"
+#include "psImageUnbin.h"
+
+// interpolate from model to background (~bresenham linear interpolation)
+// XXX this code skips the initial pixels?
+psImage *psImageUnbin (psImage *out, psImage *in, int DX, int DY, int dx, int dy)
+{
+
+    int nx = in->numCols;
+    int ny = in->numRows;
+
+    if (out == NULL)
+        psAbort ("psImageUnbin", "please supply output image");
+
+    int Nx = out->numCols;
+    int Ny = out->numRows;
+
+    psF32 **vIn = in->data.F32;
+    psF32 **vOut = out->data.F32;
+
+    for (int Iy = 0; Iy < ny-1; Iy ++) {
+        for (int Ix = 0; Ix < nx-1; Ix ++) {
+
+            float V00 = vIn[Iy+0][Ix+0];
+            float V01 = vIn[Iy+0][Ix+1];
+            float V10 = vIn[Iy+1][Ix+0];
+            float V11 = vIn[Iy+1][Ix+1];
+
+            // a single binned pixel quad
+            // (Xs,Ys) : (Xe,Ye) : binned pixel centers in unbinned coords
+            // corresponding to (Ix,Iy), (Ix+1,Iy+1)
+            int Xs = (Ix + 1)*DX - dx;
+            int Ys = (Iy + 1)*DY - dy;
+            int Xe = Xs + DX;
+            int Ye = Ys + DY;
+
+            for (int iy = Ys; (iy < Ye) && (iy < Ny); iy++) {
+                float Vxs = (V10 - V00)*(iy - Ys) / DY + V00;
+                float Vxe = (V11 - V01)*(iy - Ys) / DY + V01;
+                float dV = (Vxe - Vxs) / DX;
+                float V  = Vxs;
+                for (int ix = Xs; (ix < Xe) && (ix < Nx); ix++) {
+                    vOut[iy][ix] = V;
+                    V += dV;
+                }
+            }
+        }
+    }
+
+    // side pixels
+    int Xs = DX - dx;
+    int Xe = nx*DX - dx;
+    for (int Iy = 0; Iy < ny - 1; Iy++) {
+
+        int Ys = (Iy + 1)*DY - dy;
+        int Ye = Ys + DY;
+
+        // leading edge
+        float V0 = vIn[Iy+0][0];
+        float V1 = vIn[Iy+1][0];
+        float dV = (V1 - V0) / DY;
+        float V = V0;
+        for (int iy = Ys; (iy < Ye) && (iy < Ny); iy++) {
+            for (int ix = 0; ix < Xs; ix++) {
+                vOut[iy][ix] = V;
+            }
+            V += dV;
+        }
+
+        // trailing edge
+        V0 = vIn[Iy+0][nx-1];
+        V1 = vIn[Iy+1][nx-1];
+        dV = (V1 - V0) / DY;
+        V = V0;
+        for (int iy = Ys; (iy < Ye) && (iy < Ny); iy++) {
+            for (int ix = Xe; ix < Nx; ix++) {
+                vOut[iy][ix] = V;
+            }
+            V += dV;
+        }
+    }
+
+    // top and bottom pixels
+    int Ys = DY - dy;
+    int Ye = ny*DY - dy;
+    for (int Ix = 0; Ix < nx - 1; Ix++) {
+
+        int Xs = (Ix + 1)*DX - dx;
+        int Xe = Xs + DX;
+
+        // top edge
+        float V0 = vIn[0][Ix+0];
+        float V1 = vIn[0][Ix+1];
+        float dV = (V1 - V0) / DX;
+        for (int iy = 0; iy < Ys; iy++) {
+            float V = V0;
+            for (int ix = Xs; (ix < Xe) && (ix < Nx); ix++) {
+                vOut[iy][ix] = V;
+                V += dV;
+            }
+        }
+
+        // bottom edge
+        V0 = vIn[ny-1][Ix+0];
+        V1 = vIn[ny-1][Ix+1];
+        dV = (V1 - V0) / DX;
+        for (int iy = Ye; iy < Ny; iy++) {
+            float V = V0;
+            for (int ix = Xs; (ix < Xe) && (ix < Nx); ix++) {
+                vOut[iy][ix] = V;
+                V += dV;
+            }
+        }
+    }
+
+    // the four corners
+    {
+        float V;
+        // 0,0
+        V = vIn[0][0];
+        for (int iy = 0; iy < DY - dy; iy++)
+        {
+            for (int ix = 0; ix < DX - dx; ix++) {
+                vOut[iy][ix] = V;
+            }
+        }
+        // Nx,0
+        V = vIn[0][nx-1];
+        for (int iy = 0; iy < DY - dy; iy++)
+        {
+            for (int ix = nx*DX - dx; ix < Nx; ix++) {
+                vOut[iy][ix] = V;
+            }
+        }
+        // 0,Ny
+        V = vIn[ny-1][0];
+        for (int iy = ny*DY - dy; iy < Ny; iy++)
+        {
+            for (int ix = 0; ix < DX - dx; ix++) {
+                vOut[iy][ix] = V;
+            }
+        }
+        // Nx,Ny
+        V = vIn[nx-1][ny-1];
+        for (int iy = ny*DY - dy; iy < Ny; iy++)
+        {
+            for (int ix = nx*DX - dx; ix < Nx; ix++) {
+                vOut[iy][ix] = V;
+            }
+        }
+    }
+
+    return out;
+}
+
+// random number seed to select a fraction of the image pixels
+static psRandom *rnd = NULL;
+
+// use no more than MAX_SAMPLE_PIXELS pixels for each median box
+static int MAX_SAMPLE_PIXELS;
+
+void psImageClippedStatsInit (int nMax)
+{
+
+    MAX_SAMPLE_PIXELS = nMax;
+
+    if (!rnd) {
+        rnd = psRandomAlloc (PS_RANDOM_TAUS, 0);
+    }
+    return;
+}
+
+void psImageClippedStatsCleanup ()
+{
+    psFree (rnd);
+    rnd = NULL;
+    return;
+}
+
+psStats *psImageClippedStats (psImage *image, psImage *mask, psU8 maskValue, double fmin, double fmax)
+{
+    double value;
+    int nx = image->numCols;
+    int ny = image->numRows;
+
+    psStats *stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_QUARTILE);
+    if (nx*ny <= 0)
+        return stats;
+
+    int Nsubset = PS_MIN (MAX_SAMPLE_PIXELS, nx*ny);
+    int Npixels = nx*ny;
+
+    psVector *values = psVectorAlloc (Nsubset, PS_TYPE_F32);
+
+    float min = values->data.F32[0];
+    float max = values->data.F32[0];
+
+    int n = 0;
+    for (int i = 0; i < Nsubset; i++) {
+        double frnd = psRandomUniform (rnd);
+        int pixel = Npixels * frnd;
+        int ix = pixel % nx;
+        int iy = pixel / nx;
+
+        if (mask && mask->data.U8[iy][ix] & maskValue)
+            continue;
+
+        value = image->data.F32[iy][ix];
+        min = PS_MIN (value, min);
+        max = PS_MIN (value, max);
+        values->data.F32[n] = value;
+        n++;
+    }
+    values->n = n;
+
+    int imin = fmin * n;
+    int imax = fmax * n;
+    int npts = imax - imin + 1;
+
+    fsort (values->data.F32, n);
+
+    value = 0;
+    for (int i = imin; (i <= imax) && (i < n); i++) {
+        value += values->data.F32[i];
+    }
+    value = value / npts;
+
+    stats->robustMedian = value;
+    stats->robustUQ = values->data.F32[imax];
+    stats->robustLQ = values->data.F32[imin];
+
+    psFree (values);
+    return stats;
+    // XXX correct for selection bias??
+}
+
+void fsort (float *value, int N)
+{
+
+    int l,j,ir,i;
+    float temp;
+
+    if (N < 2)
+        return;
+    l = N >> 1;
+    ir = N - 1;
+    for (;;) {
+        if (l > 0) {
+            temp = value[--l];
+        } else {
+            temp = value[ir];
+            value[ir] = value[0];
+            if (--ir == 0) {
+                value[0] = temp;
+                return;
+            }
+        }
+        i = l;
+        j = (l << 1) + 1;
+        while (j <= ir) {
+            if (j < ir && value[j] < value[j+1])
+                ++j;
+            if (temp < value[j]) {
+                value[i]=value[j];
+                j += (i=j) + 1;
+            } else
+                j = ir + 1;
+        }
+        value[i] = temp;
+    }
+}
+
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psImageUnbin.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psImageUnbin.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psImageUnbin.h	(revision 6961)
@@ -0,0 +1,18 @@
+#ifndef PS_IMAGE_UNBIN_H
+#define PS_IMAGE_UNBIN_H
+
+#include "pslib.h"
+
+// This needs to be considered more carefully
+psImage *psImageUnbin (psImage *out, psImage *in, int DX, int DY, int dx, int dy);
+
+void psImageClippedStatsInit (int nMax);
+void psImageClippedStatsCleanup ();
+
+// my temporary image stats function; seems to be much faster than psLib???
+psStats *psImageClippedStats (psImage *image, psImage *mask, psU8 maskValue, double fmin, double fmax);
+
+// my temporary sort which is based on N.R.; seems to be faster than psLib sort???
+void fsort (float *value, int N);
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psLine.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psLine.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psLine.c	(revision 6961)
@@ -0,0 +1,55 @@
+# include <pslib.h>
+# include "psLine.h"
+
+static void psLineFree (psLine *line)
+{
+
+    if (line == NULL)
+        return;
+
+    psFree (line->line);
+    return;
+}
+
+// allocate a psLine structrue
+psLine *psLineAlloc (int Nline)
+{
+
+    psLine *line;
+    line = psAlloc (sizeof(psLine));
+    psMemSetDeallocator(line, (psFreeFunc) psLineFree);
+
+    line->Nline = 0;
+    line->NLINE = Nline;
+    line->line = psAlloc (Nline);
+    return (line);
+}
+
+bool psLineInit (psLine *line)
+{
+    if (line == NULL)
+        return (false);
+    line->Nline = 0;
+    return (true);
+}
+
+bool psLineAdd (psLine *line, char *format, ...)
+{
+
+    int Nchar;
+    va_list ap;
+
+    if (line == NULL)
+        return (false);
+
+    int nMax = line->NLINE - line->Nline;
+
+    va_start (ap, format);
+    Nchar = vsnprintf (&line->line[line->Nline], nMax, format, ap);
+    line->Nline += PS_MIN (nMax - 1, Nchar);
+    va_end (ap);
+
+    if (Nchar >= nMax)
+        return (false);
+    return (true);
+}
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psLine.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psLine.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psLine.h	(revision 6961)
@@ -0,0 +1,27 @@
+/** @file  psLine.h
+ *
+ * the psLine functions allow manipulation of fixed-length lines
+ */
+
+# ifndef PS_LINE_H
+# define PS_LINE_H
+
+// structure to carry a dynamic string
+typedef struct
+{
+    int NLINE;
+    int Nline;
+    char *line;
+}
+psLine;
+
+// allocate a line object of length Nline
+psLine      *psLineAlloc (int Nline);
+
+// (re-)init the line
+bool      psLineInit (psLine *line);
+
+// add the new string segment to the line
+bool      psLineAdd (psLine *line, char *format, ...);
+
+# endif /* PS_LINE_H */
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psMetadataItemCompare.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psMetadataItemCompare.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psMetadataItemCompare.c	(revision 6961)
@@ -0,0 +1,69 @@
+#include <stdio.h>
+#include "pslib.h"
+
+bool psMetadataItemCompare(psMetadataItem *compare, // Item to compare to the template
+                           psMetadataItem *template // The template
+                          )
+{
+
+    // First order checks
+    if (! compare || ! template) {
+        return false;
+    }
+    if (strcmp(compare->name, template->name)
+            != 0) {
+        return false;
+    }
+
+
+    #define COMPARE_CASE(TEMPLATENAME, TEMPLATETYPE, COMPARENAME, COMPARETYPENAME) \
+case PS_TYPE_##COMPARETYPENAME: { \
+        TEMPLATETYPE value = (TEMPLATETYPE)compare->data.COMPARENAME; \
+        if (value != compare->data.COMPARENAME) { \
+            return false; \
+        } \
+        return (template->data.TEMPLATENAME == value) ? true : false; \
+    }
+
+    #define TEMPLATE_CASE(TYPENAME, NAME, TYPE) \
+case PS_TYPE_##TYPENAME: \
+    switch(compare->type) { \
+        COMPARE_CASE(NAME, TYPE, B  , BOOL); \
+        COMPARE_CASE(NAME, TYPE, U8 , U8 ); \
+        COMPARE_CASE(NAME, TYPE, U16, U16); \
+        COMPARE_CASE(NAME, TYPE, U32, U32); \
+        COMPARE_CASE(NAME, TYPE, S8 , S8 ); \
+        COMPARE_CASE(NAME, TYPE, S16, S16); \
+        COMPARE_CASE(NAME, TYPE, S32, S32); \
+        COMPARE_CASE(NAME, TYPE, F32, F32); \
+        COMPARE_CASE(NAME, TYPE, F64, F64); \
+    default: \
+        return false; \
+    }
+
+
+    switch (template->type) {
+        TEMPLATE_CASE(BOOL, B  , psBool)
+        ;
+        TEMPLATE_CASE(U8,   U8 , psU8 );
+        TEMPLATE_CASE(U16,  U16, psU16);
+        TEMPLATE_CASE(U32,  U32, psU32);
+        TEMPLATE_CASE(S8 ,  S8 , psS8 );
+        TEMPLATE_CASE(S16,  S16, psS16);
+        TEMPLATE_CASE(S32,  S32, psS32);
+        TEMPLATE_CASE(F32,  F32, psF32);
+        TEMPLATE_CASE(F64,  F64, psF64);
+    case PS_DATA_STRING:
+        if (compare->type != PS_DATA_STRING) {
+            return false;
+        }
+        psTrace(__func__, 10, "Comparing '%s' with '%s'\n", compare->data.V, template->
+                data.V);
+        return (strcasecmp(compare->data.V, template->
+                           data.V) == 0) ? true : false;
+    default:
+        // Simply don't know how to compare more complex types.
+        return false;
+    }
+    psAbort(__func__, "Should never get here.\n");
+}
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psMetadataItemCompare.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psMetadataItemCompare.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psMetadataItemCompare.h	(revision 6961)
@@ -0,0 +1,12 @@
+#ifndef PS_METADATA_ITEM_COMPARE_H
+#define PS_METADATA_ITEM_COMPARE_H
+
+#include "pslib.h"
+
+bool psMetadataItemCompare(psMetadataItem *compare, // Item to compare to the template
+                           psMetadataItem *template // The template
+                          )
+;
+
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psMetadataItemParse.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psMetadataItemParse.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psMetadataItemParse.c	(revision 6961)
@@ -0,0 +1,188 @@
+#include <stdio.h>
+#include "pslib.h"
+
+# define PS_METADATA_ITEM_PARSE_NUMBER(INTYPE,OUTTYPE) \
+case PS_DATA_##INTYPE: { \
+    return (ps##OUTTYPE)(item->data.INTYPE); } \
+
+psF32 psMetadataItemParseF32(psMetadataItem *item
+                            )
+{
+    switch (item->type) {
+        PS_METADATA_ITEM_PARSE_NUMBER(F32, F32);
+        PS_METADATA_ITEM_PARSE_NUMBER(F64, F32);
+        PS_METADATA_ITEM_PARSE_NUMBER(S8,  F32);
+        PS_METADATA_ITEM_PARSE_NUMBER(S16, F32);
+        PS_METADATA_ITEM_PARSE_NUMBER(S32, F32);
+        PS_METADATA_ITEM_PARSE_NUMBER(U8,  F32);
+        PS_METADATA_ITEM_PARSE_NUMBER(U16, F32);
+        PS_METADATA_ITEM_PARSE_NUMBER(U32, F32);
+    default:
+        psError(PS_ERR_IO, true, "Item %s (%s) is not of floating point type (%x) --- treating as NaN.\n",
+                item->name, item->comment, item->type);
+        return NAN;
+    }
+}
+
+psF64 psMetadataItemParseF64(psMetadataItem *item
+                            )
+{
+    switch (item->type) {
+        PS_METADATA_ITEM_PARSE_NUMBER(F32, F64);
+        PS_METADATA_ITEM_PARSE_NUMBER(F64, F64);
+        PS_METADATA_ITEM_PARSE_NUMBER(S8,  F64);
+        PS_METADATA_ITEM_PARSE_NUMBER(S16, F64);
+        PS_METADATA_ITEM_PARSE_NUMBER(S32, F64);
+        PS_METADATA_ITEM_PARSE_NUMBER(U8,  F64);
+        PS_METADATA_ITEM_PARSE_NUMBER(U16, F64);
+        PS_METADATA_ITEM_PARSE_NUMBER(U32, F64);
+    default:
+        psError(PS_ERR_IO, true, "Item %s (%s) is not of double-precision floating point type (%x) "
+                "--- treating as NaN.\n", item->name, item->comment, item->type);
+        return NAN;
+    }
+}
+
+psU8 psMetadataItemParseU8(psMetadataItem *item
+                          )
+{
+    switch (item->type) {
+        PS_METADATA_ITEM_PARSE_NUMBER (F32, U8);
+        PS_METADATA_ITEM_PARSE_NUMBER (F64, U8);
+        PS_METADATA_ITEM_PARSE_NUMBER (S8,  U8);
+        PS_METADATA_ITEM_PARSE_NUMBER (S16, U8);
+        PS_METADATA_ITEM_PARSE_NUMBER (S32, U8);
+        PS_METADATA_ITEM_PARSE_NUMBER (U8,  U8);
+        PS_METADATA_ITEM_PARSE_NUMBER (U16, U8);
+        PS_METADATA_ITEM_PARSE_NUMBER (U32, U8);
+    default:
+        psError(PS_ERR_IO, true, "Item %s (%s) is not of integer type (%x) --- treating as zero.\n",
+                item->name, item->comment, item->type);
+        return 0;
+    }
+}
+
+psU16 psMetadataItemParseU16(psMetadataItem *item
+                            )
+{
+    switch (item->type) {
+        PS_METADATA_ITEM_PARSE_NUMBER (F32, U16);
+        PS_METADATA_ITEM_PARSE_NUMBER (F64, U16);
+        PS_METADATA_ITEM_PARSE_NUMBER (S8,  U16);
+        PS_METADATA_ITEM_PARSE_NUMBER (S16, U16);
+        PS_METADATA_ITEM_PARSE_NUMBER (S32, U16);
+        PS_METADATA_ITEM_PARSE_NUMBER (U8,  U16);
+        PS_METADATA_ITEM_PARSE_NUMBER (U16, U16);
+        PS_METADATA_ITEM_PARSE_NUMBER (U32, U16);
+    default:
+        psError(PS_ERR_IO, true, "Item %s (%s) is not of integer type (%x) --- treating as zero.\n",
+                item->name, item->comment, item->type);
+        return 0;
+    }
+}
+
+psU32 psMetadataItemParseU32(psMetadataItem *item
+                            )
+{
+    switch (item->type) {
+        PS_METADATA_ITEM_PARSE_NUMBER (F32, U32);
+        PS_METADATA_ITEM_PARSE_NUMBER (F64, U32);
+        PS_METADATA_ITEM_PARSE_NUMBER (S8,  U32);
+        PS_METADATA_ITEM_PARSE_NUMBER (S16, U32);
+        PS_METADATA_ITEM_PARSE_NUMBER (S32, U32);
+        PS_METADATA_ITEM_PARSE_NUMBER (U8,  U32);
+        PS_METADATA_ITEM_PARSE_NUMBER (U16, U32);
+        PS_METADATA_ITEM_PARSE_NUMBER (U32, U32);
+    default:
+        psError(PS_ERR_IO, true, "Item %s (%s) is not of integer type (%x) --- treating as zero.\n",
+                item->name, item->comment, item->type);
+        return 0;
+    }
+}
+
+psS8 psMetadataItemParseS8(psMetadataItem *item
+                          )
+{
+    switch (item->type) {
+        PS_METADATA_ITEM_PARSE_NUMBER(F32, S8);
+        PS_METADATA_ITEM_PARSE_NUMBER(F64, S8);
+        PS_METADATA_ITEM_PARSE_NUMBER(S8,  S8);
+        PS_METADATA_ITEM_PARSE_NUMBER(S16, S8);
+        PS_METADATA_ITEM_PARSE_NUMBER(S32, S8);
+        PS_METADATA_ITEM_PARSE_NUMBER(U8,  S8);
+        PS_METADATA_ITEM_PARSE_NUMBER(U16, S8);
+        PS_METADATA_ITEM_PARSE_NUMBER(U32, S8);
+    default:
+        psError(PS_ERR_IO, true, "Item %s (%s) is not of integer type (%x) --- treating as zero.\n",
+                item->name, item->comment, item->type);
+        return 0;
+    }
+}
+
+psS16 psMetadataItemParseS16(psMetadataItem *item
+                            )
+{
+    switch (item->type) {
+        PS_METADATA_ITEM_PARSE_NUMBER(F32, S16);
+        PS_METADATA_ITEM_PARSE_NUMBER(F64, S16);
+        PS_METADATA_ITEM_PARSE_NUMBER(S8,  S16);
+        PS_METADATA_ITEM_PARSE_NUMBER(S16, S16);
+        PS_METADATA_ITEM_PARSE_NUMBER(S32, S16);
+        PS_METADATA_ITEM_PARSE_NUMBER(U8,  S16);
+        PS_METADATA_ITEM_PARSE_NUMBER(U16, S16);
+        PS_METADATA_ITEM_PARSE_NUMBER(U32, S16);
+    default:
+        psError(PS_ERR_IO, true, "Item %s (%s) is not of integer type (%x) --- treating as zero.\n",
+                item->name, item->comment, item->type);
+        return 0;
+    }
+}
+
+psS32 psMetadataItemParseS32(psMetadataItem *item
+                            )
+{
+    switch (item->type) {
+        PS_METADATA_ITEM_PARSE_NUMBER(F32, S32);
+        PS_METADATA_ITEM_PARSE_NUMBER(F64, S32);
+        PS_METADATA_ITEM_PARSE_NUMBER(S8,  S32);
+        PS_METADATA_ITEM_PARSE_NUMBER(S16, S32);
+        PS_METADATA_ITEM_PARSE_NUMBER(S32, S32);
+        PS_METADATA_ITEM_PARSE_NUMBER(U8,  S32);
+        PS_METADATA_ITEM_PARSE_NUMBER(U16, S32);
+        PS_METADATA_ITEM_PARSE_NUMBER(U32, S32);
+    default:
+        psError(PS_ERR_IO, true, "Item %s (%s) is not of integer type (%x) --- treating as zero.\n",
+                item->name, item->comment, item->type);
+        return 0;
+    }
+}
+
+# define PS_METADATA_ITEM_PARSE_STRING(TYPE,MODE) \
+case PS_DATA_##TYPE: { \
+    psString value = NULL; \
+    psStringAppend(&value, MODE, item->data.TYPE); \
+    return value; } \
+
+psString psMetadataItemParseString(psMetadataItem *item
+                                  )
+{
+    switch (item->type) {
+    case PS_DATA_STRING:
+        return psMemIncrRefCounter(item->data.V);
+
+        PS_METADATA_ITEM_PARSE_STRING(F32, "%f");
+        PS_METADATA_ITEM_PARSE_STRING(F64, "%f");
+        PS_METADATA_ITEM_PARSE_STRING(S8,  "%d");
+        PS_METADATA_ITEM_PARSE_STRING(S16, "%d");
+        PS_METADATA_ITEM_PARSE_STRING(S32, "%d");
+        PS_METADATA_ITEM_PARSE_STRING(U8,  "%d");
+        PS_METADATA_ITEM_PARSE_STRING(U16, "%d");
+        PS_METADATA_ITEM_PARSE_STRING(U32, "%d");
+
+    default:
+        psError(PS_ERR_IO, true, "Item %s (%s) is not of string type (%x) --- treating as "
+                "undefined.\n", item->name, item->comment, item->type);
+        return psStringCopy("");
+    }
+}
+
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psMetadataItemParse.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psMetadataItemParse.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psMetadataItemParse.h	(revision 6961)
@@ -0,0 +1,19 @@
+#ifndef PS_METADATA_ITEM_PARSE_H
+#define PS_METADATA_ITEM_PARSE_H
+
+#include "pslib.h"
+
+psF32 psMetadataItemParseF32(psMetadataItem *item);
+psF64 psMetadataItemParseF64(psMetadataItem *item);
+
+psS8  psMetadataItemParseS8 (psMetadataItem *item);
+psS16 psMetadataItemParseS16(psMetadataItem *item);
+psS32 psMetadataItemParseS32(psMetadataItem *item);
+
+psU8  psMetadataItemParseU8 (psMetadataItem *item);
+psU16 psMetadataItemParseU16(psMetadataItem *item);
+psU32 psMetadataItemParseU32(psMetadataItem *item);
+
+psString psMetadataItemParseString(psMetadataItem *item);
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psPolynomialUtils.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psPolynomialUtils.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psPolynomialUtils.c	(revision 6961)
@@ -0,0 +1,161 @@
+# include <stdio.h>
+# include <strings.h>  // for strcasecmp
+# include <unistd.h>   // for unlink
+# include <pslib.h>
+# include "psPolynomialUtils.h"
+
+psPolynomial4D *psVectorChiClipFitPolynomial4D(
+    psPolynomial4D *poly,
+    psStats *stats,
+    const psVector *mask,
+    psMaskType maskValue,
+    const psVector *f,
+    const psVector *fErr,
+    const psVector *x,
+    const psVector *y,
+    const psVector *z,
+    const psVector *t)
+{
+    PS_ASSERT_POLY_NON_NULL(poly, NULL);
+    PS_ASSERT_POLY_TYPE(poly, PS_POLYNOMIAL_ORD, NULL);
+    PS_ASSERT_PTR_NON_NULL(stats, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(f, NULL);
+    PS_ASSERT_VECTOR_TYPE_F32_OR_F64(f, NULL);
+    if (mask != NULL) {
+        PS_ASSERT_VECTORS_SIZE_EQUAL(f, mask, NULL);
+        PS_ASSERT_VECTOR_TYPE(mask, PS_TYPE_U8, NULL);
+    }
+    PS_ASSERT_VECTOR_NON_NULL(x, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(f, x, NULL);
+    PS_ASSERT_VECTOR_TYPE_F32_OR_F64(x, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(y, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(f, y, NULL);
+    PS_ASSERT_VECTOR_TYPE_F32_OR_F64(y, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(z, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(f, z, NULL);
+    PS_ASSERT_VECTOR_TYPE_F32_OR_F64(z, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(t, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(f, t, NULL);
+    PS_ASSERT_VECTOR_TYPE_F32_OR_F64(t, NULL);
+    PS_ASSERT_VECTOR_NON_NULL(fErr, NULL);
+    PS_ASSERT_VECTORS_SIZE_EQUAL(fErr, mask, NULL);
+    PS_ASSERT_VECTOR_TYPE_F32_OR_F64(fErr, NULL);
+
+    // clipping range defined by min and max and/or clipSigma
+    float minClipSigma;
+    float maxClipSigma;
+    if (isfinite(stats->max)) {
+        maxClipSigma = +fabs(stats->max);
+    } else {
+        maxClipSigma = +fabs(stats->clipSigma);
+    }
+    if (isfinite(stats->min)) {
+        minClipSigma = -fabs(stats->min);
+    } else {
+        minClipSigma = -fabs(stats->clipSigma);
+    }
+    psVector *fit   = NULL;
+    psVector *resid = psVectorAlloc (x->n, PS_TYPE_F64);
+
+    // eventual expansion: user supplies one of various stats option pairs,
+    // eg (SAMPLE_MEAN | SAMPLE_STDEV) and the correct pair is used to
+    // evaluate the clipping sigma
+    // for now, for the SAMPLE_MEDIAN and SAMPLE_STDEV to be used
+    stats->options |= (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+
+    for (int N = 0; N < stats->clipIter; N++) {
+        int Nkeep = 0;
+
+        poly = psVectorFitPolynomial4D (poly, mask, maskValue, f, fErr, x, y, z, t);
+        fit = psPolynomial4DEvalVector (poly, x, y, z, t);
+        resid = (psVector *) psBinaryOp (resid, (void *) f, "-", (void *) fit);
+
+        stats  = psVectorStats (stats, resid, NULL, mask, maskValue);
+        psTrace (__func__, 5, "resid stats: %f +/- %f\n", stats->sampleMedian, stats->sampleStdev);
+
+        // set mask if pts are not valid
+        // we are masking out any point which is out of range
+        // recovery is not allowed with this scheme
+        for (int i = 0; i < resid->n; i++) {
+            if ((mask != NULL) && (mask->data.U8[i] & maskValue)) {
+                continue;
+            }
+            float sigma = hypot (psVectorGet (fErr, i), stats->sampleStdev);
+            if (resid->data.F64[i] - stats->sampleMedian > sigma*maxClipSigma) {
+                if (mask != NULL) {
+                    mask->data.U8[i] |= 0x01;
+                }
+                continue;
+            }
+            if (resid->data.F64[i] - stats->sampleMedian < sigma*minClipSigma) {
+                if (mask != NULL) {
+                    mask->data.U8[i] |= 0x01;
+                }
+                continue;
+            }
+            Nkeep ++;
+        }
+
+        psTrace (__func__, 4, "keeping %d of %d pts for fit\n",
+                 Nkeep, x->n);
+
+        stats->clippedNvalues = Nkeep;
+        psFree (fit);
+    }
+    // Free local temporary variables
+    psFree (resid);
+
+    if (poly == NULL) {
+        psError(PS_ERR_UNKNOWN, true, "Could not fit a polynomial to the data.  Returning NULL.\n");
+        return(NULL);
+    }
+    return(poly);
+}
+
+psPolynomial2D *psImageBicubeFit (psImage *image, int x, int y)
+{
+
+    int ix = x - image->col0;
+    int iy = y - image->row0;
+
+    psF32 *Fm = &image->data.F32[iy - 1][ix];
+    psF32 *Fo = &image->data.F32[iy + 0][ix];
+    psF32 *Fp = &image->data.F32[iy + 1][ix];
+
+    double Fxm = Fm[-1] + Fo[-1] + Fp[-1];
+    double Fxp = Fm[+1] + Fo[+1] + Fp[+1];
+    double Fym = Fm[-1] + Fm[+0] + Fm[+1];
+    double Fyp = Fp[-1] + Fp[+0] + Fp[+1];
+    double Foo = Fym + Fyp + Fo[-1] + Fo[+0] + Fo[+1];
+
+    psPolynomial2D *poly = psPolynomial2DAlloc (PS_POLYNOMIAL_ORD, 2, 2);
+    poly->mask[2][2] = 1;
+    poly->mask[1][2] = 1;
+    poly->mask[2][1] = 1;
+
+    poly->coeff[0][0] = Foo*(5.0/9.0) - (Fxp + Fxm)/3.0 - (Fyp + Fym)/3.0 ;
+
+    poly->coeff[1][0] = (Fxp - Fxm)/6.0;
+    poly->coeff[0][1] = (Fyp - Fym)/6.0;
+
+    poly->coeff[2][0] = (Fxp + Fxm)/2.0 - Foo/3.0;
+    poly->coeff[0][2] = (Fyp + Fym)/2.0 - Foo/3.0;
+
+    poly->coeff[1][1] = (Fp[+1] + Fm[-1] - Fm[+1] - Fp[-1])/4.0;
+
+    return (poly);
+}
+
+psPlane psImageBicubeMin (psPolynomial2D *poly)
+{
+
+    psPlane min;
+
+    min.xErr = min.yErr = 0;
+
+    double det = 4*poly->coeff[2][0]*poly->coeff[0][2] - PS_SQR(poly->coeff[1][1]);
+
+    min.x = (poly->coeff[1][1]*poly->coeff[0][1] - 2*poly->coeff[0][2]*poly->coeff[1][0]) / det;
+    min.y = (poly->coeff[1][1]*poly->coeff[1][0] - 2*poly->coeff[2][0]*poly->coeff[0][1]) / det;
+    return (min);
+}
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psPolynomialUtils.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psPolynomialUtils.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psPolynomialUtils.h	(revision 6961)
@@ -0,0 +1,28 @@
+/** @file  psPolynomialUtils.h
+ *
+ * extra psPolynomial-related functions
+ */
+
+# ifndef PS_POLY_UTILS_H
+# define PS_POLY_UTILS_H
+
+// perform vector clip-fit based on significance of deviations
+psPolynomial4D *psVectorChiClipFitPolynomial4D(
+    psPolynomial4D *poly,
+    psStats *stats,
+    const psVector *mask,
+    psMaskType maskValue,
+    const psVector *f,
+    const psVector *fErr,
+    const psVector *x,
+    const psVector *y,
+    const psVector *z,
+    const psVector *t);
+
+// fit a 2D 2nd order polynomial to the 9 pixels centered on (x,y)
+psPolynomial2D *psImageBicubeFit (psImage *image, int x, int y);
+
+// detemine the min(max) of the special 2D 2nd order polynomial
+psPlane psImageBicubeMin (psPolynomial2D *poly);
+
+# endif /* PS_POLY_UTILS_H */
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psRegionIsBad.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psRegionIsBad.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psRegionIsBad.c	(revision 6961)
@@ -0,0 +1,21 @@
+#include <stdio.h>
+#include "pslib.h"
+
+bool psRegionIsBad(const psRegion region)
+{
+    return isnan(region.x0) || isnan(region.x1) || isnan(region.y0) || isnan(region.y1);
+}
+
+bool psRegionIsNaN (psRegion region)
+{
+
+    if (!isfinite(region.x0))
+        return true;
+    if (!isfinite(region.x1))
+        return true;
+    if (!isfinite(region.y0))
+        return true;
+    if (!isfinite(region.y1))
+        return true;
+    return false;
+}
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psRegionIsBad.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psRegionIsBad.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psRegionIsBad.h	(revision 6961)
@@ -0,0 +1,12 @@
+#ifndef PS_REGION_IS_BAD_H
+#define PS_REGION_IS_BAD_H
+
+#include "pslib.h"
+
+bool psRegionIsBad(const psRegion region);
+
+// test is any element of the region is not finite
+bool psRegionIsNaN (psRegion region // test for this region
+                   );
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psSparse.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psSparse.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psSparse.c	(revision 6961)
@@ -0,0 +1,253 @@
+# include <pslib.h>
+# include "psSparse.h"
+
+static void psSparseFree (psSparse *sparse)
+{
+    if (sparse == NULL)
+        return;
+    psFree (sparse->Aij);
+    psFree (sparse->Bfj);
+    psFree (sparse->Qii);
+    psFree (sparse->Si);
+    psFree (sparse->Sj);
+    return;
+}
+
+// allocate a sparse matrix container for Nrows, with Nelem slots allocated
+psSparse *psSparseAlloc (int Nrows, int Nelem)
+{
+
+    psSparse *sparse = (psSparse *) psAlloc (sizeof(psSparse));
+    sparse->Aij = psVectorAlloc (Nelem, PS_DATA_F32);
+    sparse->Si  = psVectorAlloc (Nelem, PS_DATA_S32);
+    sparse->Sj  = psVectorAlloc (Nelem, PS_DATA_S32);
+
+    sparse->Aij->n = 0;
+    sparse->Si->n  = 0;
+    sparse->Sj->n  = 0;
+    sparse->Nelem = 0;
+
+    sparse->Bfj = psVectorAlloc (Nrows, PS_DATA_F32);
+    sparse->Qii = psVectorAlloc (Nrows, PS_DATA_F32);
+    sparse->Bfj->n = Nrows;
+    sparse->Qii->n = Nrows;
+
+    sparse->Nrows = Nrows;
+    psMemSetDeallocator(sparse, (psFreeFunc) psSparseFree);
+    return (sparse);
+}
+
+// user should only add elements above the diagonal, but we don't check this
+void psSparseMatrixElement (psSparse *sparse, int i, int j, float value)
+{
+
+    int k;
+
+    if (i < j) {
+        fprintf (stderr, "*** error: subdiagonal element ***\n");
+        return;
+    }
+
+    if (i == j) {
+        // add to the diagonal
+        sparse->Qii->data.F32[i] = value;
+
+        // check vectors lengths and extend if needed
+        if (sparse->Nelem >= sparse->Aij->nalloc) {
+            psVectorRealloc (sparse->Aij, sparse->Aij->nalloc + 100);
+            psVectorRealloc (sparse->Si,  sparse->Si->nalloc + 100);
+            psVectorRealloc (sparse->Sj,  sparse->Sj->nalloc + 100);
+        }
+
+        k = sparse->Nelem;
+        sparse->Aij->data.F32[k] = value;
+        sparse->Si->data.S32[k]  = i;
+        sparse->Sj->data.S32[k]  = j;
+
+        sparse->Nelem ++;
+        sparse->Aij->n ++;
+        sparse->Si->n ++;
+        sparse->Sj->n ++;
+    } else {
+        // check vectors lengths and extend if needed
+        if (sparse->Nelem >= sparse->Aij->nalloc - 1) {
+            psVectorRealloc (sparse->Aij, sparse->Aij->nalloc + 100);
+            psVectorRealloc (sparse->Si,  sparse->Si->nalloc + 100);
+            psVectorRealloc (sparse->Sj,  sparse->Sj->nalloc + 100);
+        }
+
+        k = sparse->Nelem;
+        sparse->Aij->data.F32[k] = value;
+        sparse->Si->data.S32[k]  = i;
+        sparse->Sj->data.S32[k]  = j;
+        k++;
+
+        sparse->Aij->data.F32[k] = value;
+        sparse->Si->data.S32[k]  = j;
+        sparse->Sj->data.S32[k]  = i;
+
+        sparse->Nelem  += 2;
+        sparse->Aij->n += 2;
+        sparse->Si->n  += 2;
+        sparse->Sj->n  += 2;
+    }
+    return;
+}
+
+void psSparseVectorElement (psSparse *sparse, int i, float value)
+{
+
+    sparse->Bfj->data.F32[i] = value;
+    return;
+}
+
+// multiple A * x
+psVector *psSparseMatrixTimesVector (psVector *output, psSparse *matrix, psVector *vector)
+{
+
+    int i, Nelem;
+    float F;
+
+    if (output == NULL) {
+        output = psVectorAlloc (vector->n, PS_DATA_F32);
+        output->n = vector->n;
+    }
+
+    Nelem = 0;
+    for (int j = 0; j < vector->n; j++) {
+        F = 0;
+        while (matrix->Sj->data.S32[Nelem] == j) {
+            i = matrix->Si->data.S32[Nelem];
+            F += vector->data.F32[i] * matrix->Aij->data.F32[Nelem];
+            Nelem++;
+        }
+        output->data.F32[j] = F;
+    }
+    return (output);
+}
+
+psVector *psSparseSolve (psVector *guess, psSparseConstraint constraint, psSparse *sparse, int Niter)
+{
+
+    psF32 dG;
+
+    psVector *Qii = sparse->Qii;
+    psVector *Bfj = sparse->Bfj;
+
+    guess = psVectorCopy (guess, Bfj, PS_DATA_F32);
+
+    // temporary storage for intermediate results
+    psVector *dQ = psVectorAlloc (guess->n, PS_DATA_F32);
+    dQ->n = guess->n;
+
+    for (int j = 0; j < Niter; j++) {
+        dQ = psSparseMatrixTimesVector (dQ, sparse, guess);
+        for (int i = 0; i < dQ->n; i++) {
+            dG = (dQ->data.F32[i] - Bfj->data.F32[i]) / Qii->data.F32[i];
+            if (fabs (dG) > constraint.paramDelta) {
+                if (dG > 0) {
+                    dG = +constraint.paramDelta;
+                } else {
+                    dG = -constraint.paramDelta;
+                }
+            }
+            guess->data.F32[i] -= dG;
+            guess->data.F32[i] = PS_MAX (guess->data.F32[i], constraint.paramMin);
+            guess->data.F32[i] = PS_MIN (guess->data.F32[i], constraint.paramMax);
+        }
+    }
+    psFree (dQ);
+    return (guess);
+}
+
+void psSparseResort (psSparse *sparse)
+{
+
+    int Nelem = sparse->Nelem;
+
+    psVector *index = psVectorSortIndex (NULL, sparse->Sj);
+    psVector *Aij = sparse->Aij;
+    psVector *Si = sparse->Si;
+    psVector *Sj = sparse->Sj;
+
+    // allocate new temporary vectors
+    psVector *tAij = psVectorAlloc (Nelem, PS_DATA_F32);
+    psVector *tSi  = psVectorAlloc (Nelem, PS_DATA_S32);
+    psVector *tSj  = psVectorAlloc (Nelem, PS_DATA_S32);
+    tAij->n = tSi->n = tSj->n = Nelem;
+
+    for (int i = 0; i < Nelem; i++) {
+        int j = index->data.U32[i];
+        tAij->data.F32[i] = Aij->data.F32[j];
+        tSi->data.S32[i]  = Si->data.S32[j];
+        tSj->data.S32[i]  = Sj->data.S32[j];
+    }
+    psFree (index);
+    psFree (Aij);
+    psFree (Si);
+    psFree (Sj);
+
+    sparse->Aij = tAij;
+    sparse->Si = tSi;
+    sparse->Sj = tSj;
+    return;
+}
+
+void psSparseMatrixTest ()
+{
+
+    // build a sparse matrix
+    psSparse *sparse = psSparseAlloc (3, 9);
+
+    psSparseMatrixElement (sparse, 0, 0, 3.0);
+    psSparseMatrixElement (sparse, 1, 1, 2.0);
+    psSparseMatrixElement (sparse, 2, 2, 1.0);
+
+    psSparseMatrixElement (sparse, 1, 0, 0.1);
+    psSparseMatrixElement (sparse, 2, 0, -0.1);
+
+    psSparseResort (sparse);
+    for (int i = 0; i < sparse->Nelem; i++) {
+        fprintf (stderr, "%d %d %f\n",
+                 sparse->Si->data.S32[i],
+                 sparse->Sj->data.S32[i],
+                 sparse->Aij->data.F32[i]);
+    }
+
+    psVector *x = psVectorAlloc (3, PS_DATA_F32);
+    x->data.F32[0] = 3;
+    x->data.F32[1] = 5;
+    x->data.F32[2] = 7;
+    x->n = 3;
+    fprintf (stderr, "x: %f %f %f\n", x->data.F32[0], x->data.F32[1], x->data.F32[2]);
+
+
+    psVector *B = psSparseMatrixTimesVector (NULL, sparse, x);
+    fprintf (stderr, "B: %f %f %f\n", B->data.F32[0], B->data.F32[1], B->data.F32[2]);
+
+    sparse->Bfj->data.F32[0] = B->data.F32[0];
+    sparse->Bfj->data.F32[1] = B->data.F32[1];
+    sparse->Bfj->data.F32[2] = B->data.F32[2];
+
+    psSparseConstraint constraint;
+    constraint.paramMin   = -1e8;
+    constraint.paramMax   = +1e8;
+    constraint.paramDelta = +1e8;
+
+    x = psSparseSolve (x, constraint, sparse, 0);
+    fprintf (stderr, "x: %f %f %f\n", x->data.F32[0], x->data.F32[1], x->data.F32[2]);
+
+    x = psSparseSolve (x, constraint, sparse, 1);
+    fprintf (stderr, "x: %f %f %f\n", x->data.F32[0], x->data.F32[1], x->data.F32[2]);
+
+    x = psSparseSolve (x, constraint, sparse, 2);
+    fprintf (stderr, "x: %f %f %f\n", x->data.F32[0], x->data.F32[1], x->data.F32[2]);
+
+    x = psSparseSolve (x, constraint, sparse, 3);
+    fprintf (stderr, "x: %f %f %f\n", x->data.F32[0], x->data.F32[1], x->data.F32[2]);
+
+    x = psSparseSolve (x, constraint, sparse, 4);
+    fprintf (stderr, "x: %f %f %f\n", x->data.F32[0], x->data.F32[1], x->data.F32[2]);
+    return;
+}
+
Index: /tags/rel11_ifa_pre0/psModules/src/pslib/psSparse.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/pslib/psSparse.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/pslib/psSparse.h	(revision 6961)
@@ -0,0 +1,62 @@
+
+/** @file  psSparse.h
+ *
+ * functions to manipulate sparse matrices equations
+ *  
+ */
+
+# ifndef PS_SPARSE_H
+# define PS_SPARSE_H
+
+// constraints to limit the range of the matrix equation solution
+typedef struct
+{
+    double paramDelta;
+    double paramMin;
+    double paramMax;
+}
+psSparseConstraint;
+
+// a sparse matrix equation: A x = Bf
+// Aij contains the populated elements of the matrix
+// Bfj contains the elements of the vector Bf
+// Qii contains the diagonal elements of Aij
+// Si contains the i-index values of Aij
+// Sj contains the j-index values of Aij
+typedef struct
+{
+    psVector *Aij;
+    psVector *Bfj;
+    psVector *Qii;
+    psVector *Si;
+    psVector *Sj;
+    int Nelem;
+    int Nrows;
+}
+psSparse;
+
+// allocate a sparse matrix structure
+psSparse *psSparseAlloc (int Nrows, int Nelem);
+
+// add a new matrix element
+// user should only add elements above the diagonal
+void psSparseMatrixElement (psSparse *sparse, int i, int j, float value);
+
+// define a new sparse matrix equation vector element
+void psSparseVectorElement (psSparse *sparse, int i, float value);
+
+// perform the operation matrix * vector on a sparse matrix and a vector
+psVector *psSparseMatrixTimesVector (psVector *output, psSparse *matrix, psVector *vector);
+
+// re-sort a sparse matrix to have all elements in index order rather than insertion order
+// call this before solving, but after populating matrix and vector
+void psSparseResort (psSparse *sparse);
+
+// solve the equation A x = Bf for the value of x
+// a good starting guess is the vector Bf
+psVector *psSparseSolve (psVector *guess, psSparseConstraint constraint, psSparse *sparse, int Niter);
+
+// test of the sparse matrix solutions (move to test suite)
+void psSparseMatrixTest ();
+
+# endif /* PS_SPARSE_H */
Index: /tags/rel11_ifa_pre0/psModules/src/psmodules.h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/src/psmodules.h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/src/psmodules.h	(revision 6961)
@@ -0,0 +1,77 @@
+#ifndef PS_MODULES_H
+#define PS_MODULES_H
+
+// XXX should this be included explicitly?
+#include <pslib.h>
+
+// the following headers are from psModule:pslib
+#include <psImageJpeg.h>
+#include <psImageFlip.h>
+#include <psImageUnbin.h>
+#include <psLine.h>
+#include <psPolynomialUtils.h>
+#include <psSparse.h>
+#include <psEllipse.h>
+#include <psAdditionals.h>
+#include <psRegionIsBad.h>
+#include <psMetadataItemParse.h>
+
+// the following headers are from psModule:config
+#include <pmConfig.h>
+
+// the following headers are from psModule:astrom
+#include <pmConcepts.h>
+#include <pmConceptsRead.h>
+#include <pmConceptsStandard.h>
+#include <pmConceptsWrite.h>
+#include <pmHDU.h>
+#include <pmHDUUtils.h>
+#include <pmFPA.h>
+#include <pmFPAview.h>
+#include <pmFPAfile.h>
+#include <pmFPARead.h>
+#include <pmFPAConstruct.h>
+#include <pmFPACopy.h>
+#include <pmFPAHeader.h>
+#include <pmFPARead.h>
+#include <pmFPAWrite.h>
+#include <pmFPA_JPEG.h>
+
+#include <pmReadout.h>
+// #include <pmChipMosaic.h>
+// #include <pmFPAAstrometry.h>
+
+// the following headers are from psModule:detrend
+#include <pmFlatField.h>
+#include <pmFlatFieldErrors.h>
+#include <pmFringeStats.h>
+#include <pmMaskBadPixels.h>
+#include <pmMaskBadPixelsErrors.h>
+#include <pmNonLinear.h>
+
+// the following headers are from psModule:imcombine
+#include <pmImageCombine.h>
+// #include <pmReadoutCombine.h>
+
+// the following headers are from psModule:detrend
+#include <pmSubtractBias.h>
+#include <pmImageSubtract.h>
+// #include <pmSubtractSky.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>
+
+#endif
Index: /tags/rel11_ifa_pre0/psModules/templates/c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/templates/c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/templates/c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/templates/h
===================================================================
--- /tags/rel11_ifa_pre0/psModules/templates/h	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/templates/h	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/.cvsignore	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/FullUnitTest
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/FullUnitTest	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/FullUnitTest	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/Makefile.am	(revision 6961)
@@ -0,0 +1,17 @@
+# Makefile for psModule tests
+
+SUBDIRS = $(SRCDIRS)
+
+EXTRA_DIST = \
+	runTest \
+	FullUnitTest
+
+all:
+	chmod a+x $(srcdir)/FullUnitTest
+
+test: check
+
+tests: $(TESTS)
+
+CLEANFILES = $(TESTS)
+
Index: /tags/rel11_ifa_pre0/psModules/test/astrom/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/astrom/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/astrom/.cvsignore	(revision 6961)
@@ -0,0 +1,7 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmAstrometry
+tst_pmAstrometry01
Index: /tags/rel11_ifa_pre0/psModules/test/astrom/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/astrom/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/astrom/Makefile.am	(revision 6961)
@@ -0,0 +1,24 @@
+# Makefile for psModule tests
+
+AM_LDFLAGS = -L$(top_builddir)/src -lpsmodule $(PSMODULE_LIBS)
+AM_CFLAGS  = @AM_CFLAGS@ $(PSMODULE_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/rel11_ifa_pre0/psModules/test/astrom/tst_pmAstrometry.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/astrom/tst_pmAstrometry.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/astrom/tst_pmAstrometry.c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/astrom/tst_pmAstrometry01.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/astrom/tst_pmAstrometry01.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/astrom/tst_pmAstrometry01.c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/astrom/verified/tst_pmAstrometry.stderr
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/astrom/verified/tst_pmAstrometry.stderr	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/astrom/verified/tst_pmAstrometry.stderr	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/camera/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/camera/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/camera/.cvsignore	(revision 6961)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmAstrometry
Index: /tags/rel11_ifa_pre0/psModules/test/camera/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/camera/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/camera/Makefile.am	(revision 6961)
@@ -0,0 +1,16 @@
+# Makefile for psModule tests
+
+AM_LDFLAGS = -L$(top_builddir)/src -lpsmodule $(PSMODULE_LIBS)
+AM_CFLAGS  = @AM_CFLAGS@ $(PSMODULE_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/rel11_ifa_pre0/psModules/test/config/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/config/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/config/.cvsignore	(revision 6961)
@@ -0,0 +1,7 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmConfig
+SampleIPPConfig
Index: /tags/rel11_ifa_pre0/psModules/test/config/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/config/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/config/Makefile.am	(revision 6961)
@@ -0,0 +1,27 @@
+# Makefile for psModule tests
+
+AM_LDFLAGS = -L$(top_builddir)/src -lpsmodule $(PSMODULE_LIBS)
+AM_CFLAGS  = @AM_CFLAGS@ $(PSMODULE_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/rel11_ifa_pre0/psModules/test/config/data/SampleIPPConfig
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/config/data/SampleIPPConfig	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/config/data/SampleIPPConfig	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/config/tst_pmConfig.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/config/tst_pmConfig.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/config/tst_pmConfig.c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/config/verified/tst_pmConfig.stderr
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/config/verified/tst_pmConfig.stderr	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/config/verified/tst_pmConfig.stderr	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/config/verified/tst_pmConfig.stdout
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/config/verified/tst_pmConfig.stdout	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/config/verified/tst_pmConfig.stdout	(revision 6961)
@@ -0,0 +1,2 @@
+----------------------------------------------------------------
+Calling pmConfigRead() with acceptable arguments.
Index: /tags/rel11_ifa_pre0/psModules/test/detrend/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/detrend/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/detrend/.cvsignore	(revision 6961)
@@ -0,0 +1,9 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmFlatField
+tst_pmMaskBadPixels
+tst_pmNonLinear
+.tmp_tst_pmNonLinearLookupFile
Index: /tags/rel11_ifa_pre0/psModules/test/detrend/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/detrend/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/detrend/Makefile.am	(revision 6961)
@@ -0,0 +1,27 @@
+# Makefile for psModule tests
+
+AM_LDFLAGS = -L$(top_builddir)/src -lpsmodule $(PSMODULE_LIBS)
+AM_CFLAGS  = @AM_CFLAGS@ $(PSMODULE_CFLAGS) $(SRCINC)
+AM_CPPFLAGS = $(SRCINC) $(PSLIB_CFLAGS)
+
+TESTS = \
+    tst_pmFlatField \
+    tst_pmMaskBadPixels \
+    tst_pmNonLinear
+
+tst_pmFlatField_SOURCES = tst_pmFlatField.c
+tst_pmMaskBadPixels_SOURCES = tst_pmMaskBadPixels.c
+tst_pmNonLinear_SOURCES = tst_pmNonLinear.c
+
+check_PROGRAMS = $(TESTS)
+
+TESTS_ENVIRONMENT = perl $(top_srcdir)/test/runTest --verified=$(srcdir)/verified
+
+tests: $(TESTS)
+
+EXTRA_DIST = verified
+
+CLEANFILES = $(TESTS) temp/* .tmp_tst_pmNonLinearLookupFile
+
+
+test: check
Index: /tags/rel11_ifa_pre0/psModules/test/detrend/tst_pmFlatField.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/detrend/tst_pmFlatField.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/detrend/tst_pmFlatField.c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/detrend/tst_pmMaskBadPixels.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/detrend/tst_pmMaskBadPixels.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/detrend/tst_pmMaskBadPixels.c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/detrend/tst_pmNonLinear.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/detrend/tst_pmNonLinear.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/detrend/tst_pmNonLinear.c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmFlatField.stderr
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmFlatField.stderr	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmFlatField.stderr	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmFlatField.stdout
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmFlatField.stdout	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmFlatField.stdout	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmMaskBadPixels.stderr
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmMaskBadPixels.stderr	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmMaskBadPixels.stderr	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmMaskBadPixels.stdout
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmMaskBadPixels.stdout	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmMaskBadPixels.stdout	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmNonLinear.stderr
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmNonLinear.stderr	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmNonLinear.stderr	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmNonLinear.stdout
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmNonLinear.stdout	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/detrend/verified/tst_pmNonLinear.stdout	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/imcombine/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imcombine/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imcombine/.cvsignore	(revision 6961)
@@ -0,0 +1,7 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmImageCombine
+tst_pmReadoutCombine
Index: /tags/rel11_ifa_pre0/psModules/test/imcombine/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imcombine/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imcombine/Makefile.am	(revision 6961)
@@ -0,0 +1,23 @@
+# Makefile for psModule tests
+
+AM_LDFLAGS = -L$(top_builddir)/src -lpsmodule $(PSMODULE_LIBS)
+AM_CFLAGS  = @AM_CFLAGS@ $(PSMODULE_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/rel11_ifa_pre0/psModules/test/imcombine/tst_pmImageCombine.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imcombine/tst_pmImageCombine.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imcombine/tst_pmImageCombine.c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/imcombine/tst_pmReadoutCombine.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imcombine/tst_pmReadoutCombine.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imcombine/tst_pmReadoutCombine.c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/imcombine/verified/tst_pmImageCombine.stderr
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imcombine/verified/tst_pmImageCombine.stderr	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imcombine/verified/tst_pmImageCombine.stderr	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/imcombine/verified/tst_pmImageCombine.stdout
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imcombine/verified/tst_pmImageCombine.stdout	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imcombine/verified/tst_pmImageCombine.stdout	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/imcombine/verified/tst_pmReadoutCombine.stderr
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imcombine/verified/tst_pmReadoutCombine.stderr	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imcombine/verified/tst_pmReadoutCombine.stderr	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/imcombine/verified/tst_pmReadoutCombine.stdout
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imcombine/verified/tst_pmReadoutCombine.stdout	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imcombine/verified/tst_pmReadoutCombine.stdout	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/imsubtract/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imsubtract/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imsubtract/.cvsignore	(revision 6961)
@@ -0,0 +1,8 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmImageSubtract
+tst_pmSubtractBias
+tst_pmSubtractSky
Index: /tags/rel11_ifa_pre0/psModules/test/imsubtract/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imsubtract/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imsubtract/Makefile.am	(revision 6961)
@@ -0,0 +1,26 @@
+# Makefile for psModule tests
+
+AM_LDFLAGS = -L$(top_builddir)/src -lpsmodule $(PSMODULE_LIBS)
+AM_CFLAGS  = @AM_CFLAGS@ $(PSMODULE_CFLAGS) $(SRCINC)
+AM_CPPFLAGS = $(SRCINC) $(PSLIB_CFLAGS)
+
+TESTS = \
+    tst_pmImageSubtract \
+    tst_pmSubtractBias \
+    tst_pmSubtractSky
+
+tst_pmImageSubtract_SOURCES = tst_pmImageSubtract.c
+tst_pmSubtractBias_SOURCES = tst_pmSubtractBias.c
+tst_pmSubtractSky_SOURCES = tst_pmSubtractSky.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/rel11_ifa_pre0/psModules/test/imsubtract/tst_pmImageSubtract.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imsubtract/tst_pmImageSubtract.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imsubtract/tst_pmImageSubtract.c	(revision 6961)
@@ -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.2 $ $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 "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/rel11_ifa_pre0/psModules/test/imsubtract/tst_pmSubtractBias.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imsubtract/tst_pmSubtractBias.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imsubtract/tst_pmSubtractBias.c	(revision 6961)
@@ -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.7 $ $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 "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/rel11_ifa_pre0/psModules/test/imsubtract/tst_pmSubtractSky.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imsubtract/tst_pmSubtractSky.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imsubtract/tst_pmSubtractSky.c	(revision 6961)
@@ -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.4 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2006-02-06 21:23:46 $
+ *
+ *  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/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmImageSubtract.stderr
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmImageSubtract.stderr	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmImageSubtract.stderr	(revision 6961)
@@ -0,0 +1,96 @@
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmImageSubtract.c                                      *
+*            TestPoint: Test Point Driver{pmSubtractionKernelsAllocPOIS()}         *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (Test Point Driver{pmSubtractionKernelsAllocPOIS()} | tst_pmImageSubtract.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmImageSubtract.c                                      *
+*            TestPoint: Test Point Driver{pmSubtractionKernelsAllocISIS()}         *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (Test Point Driver{pmSubtractionKernelsAllocISIS()} | tst_pmImageSubtract.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmImageSubtract.c                                      *
+*            TestPoint: Test Point Driver{pmSubtractionFindStamps()}               *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|E|pmSubtractionFindStamps (FILE:LINENO)
+    Unallowable operation: psImage image or its data is NULL.
+<HOST>|E|pmSubtractionFindStamps (FILE:LINENO)
+    Error: xNum is 0 or less.
+<HOST>|E|pmSubtractionFindStamps (FILE:LINENO)
+    Error: yNum is 0 or less.
+<HOST>|E|pmSubtractionFindStamps (FILE:LINENO)
+    Error: border is 0 or less.
+<HOST>|E|pmSubtractionFindStamps (FILE:LINENO)
+    Unallowable operation: psImage image or its data is NULL.
+<HOST>|E|pmSubtractionFindStamps (FILE:LINENO)
+    Error: xNum is 0 or less.
+<HOST>|E|pmSubtractionFindStamps (FILE:LINENO)
+    Error: yNum is 0 or less.
+<HOST>|E|pmSubtractionFindStamps (FILE:LINENO)
+    Error: border is 0 or less.
+
+---> TESTPOINT PASSED (Test Point Driver{pmSubtractionFindStamps()} | tst_pmImageSubtract.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmImageSubtract.c                                      *
+*            TestPoint: Test Point Driver{pmSubtractionCalculateEquation()}        *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|E|pmSubtractionCalculateEquation (FILE:LINENO)
+    Unallowable operation: stamps is NULL.
+<HOST>|E|pmSubtractionCalculateEquation (FILE:LINENO)
+    Unallowable operation: psImage reference or its data is NULL.
+<HOST>|E|pmSubtractionCalculateEquation (FILE:LINENO)
+    Unallowable operation: psImage input or its data is NULL.
+<HOST>|E|pmSubtractionCalculateEquation (FILE:LINENO)
+    Unallowable operation: kernels is NULL.
+<HOST>|E|pmSubtractionSolveEquation (FILE:LINENO)
+    Unallowable operation: stamps is NULL.
+<HOST>|E|pmSubtractionKernelImage (FILE:LINENO)
+    Unallowable operation: psVector solution or its data is NULL.
+<HOST>|E|pmSubtractionKernelImage (FILE:LINENO)
+    Unallowable operation: kernels is NULL.
+<HOST>|E|pmSubtractionKernelImage (FILE:LINENO)
+    Error: x, -2.000000, is out of range.  Must be between -1.000000 and 1.000000.
+<HOST>|E|pmSubtractionKernelImage (FILE:LINENO)
+    Error: x, 2.000000, is out of range.  Must be between -1.000000 and 1.000000.
+<HOST>|E|pmSubtractionKernelImage (FILE:LINENO)
+    Error: y, -2.000000, is out of range.  Must be between -1.000000 and 1.000000.
+<HOST>|E|pmSubtractionKernelImage (FILE:LINENO)
+    Error: y, 2.000000, is out of range.  Must be between -1.000000 and 1.000000.
+<HOST>|E|pmSubtractionCalculateEquation (FILE:LINENO)
+    Unallowable operation: stamps is NULL.
+<HOST>|E|pmSubtractionCalculateEquation (FILE:LINENO)
+    Unallowable operation: psImage reference or its data is NULL.
+<HOST>|E|pmSubtractionCalculateEquation (FILE:LINENO)
+    Unallowable operation: psImage input or its data is NULL.
+<HOST>|E|pmSubtractionCalculateEquation (FILE:LINENO)
+    Unallowable operation: kernels is NULL.
+<HOST>|E|pmSubtractionSolveEquation (FILE:LINENO)
+    Unallowable operation: stamps is NULL.
+<HOST>|E|pmSubtractionKernelImage (FILE:LINENO)
+    Unallowable operation: psVector solution or its data is NULL.
+<HOST>|E|pmSubtractionKernelImage (FILE:LINENO)
+    Unallowable operation: kernels is NULL.
+<HOST>|E|pmSubtractionKernelImage (FILE:LINENO)
+    Error: x, -2.000000, is out of range.  Must be between -1.000000 and 1.000000.
+<HOST>|E|pmSubtractionKernelImage (FILE:LINENO)
+    Error: x, 2.000000, is out of range.  Must be between -1.000000 and 1.000000.
+<HOST>|E|pmSubtractionKernelImage (FILE:LINENO)
+    Error: y, -2.000000, is out of range.  Must be between -1.000000 and 1.000000.
+<HOST>|E|pmSubtractionKernelImage (FILE:LINENO)
+    Error: y, 2.000000, is out of range.  Must be between -1.000000 and 1.000000.
+
+---> TESTPOINT PASSED (Test Point Driver{pmSubtractionCalculateEquation()} | tst_pmImageSubtract.c)
+
Index: /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmImageSubtract.stdout
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmImageSubtract.stdout	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmImageSubtract.stdout	(revision 6961)
@@ -0,0 +1,73 @@
+Testing pmSubtractionKernelsAllocPOIS(1, 1)
+Testing pmSubtractionKernelsAllocPOIS(2, 3)
+Testing pmSubtractionKernelsAllocPOIS(3, 4)
+Testing pmSubtractionFindStamps(100, 100, 2, 2, 2)
+Calling with a NULL psImage.  Should generate error, return NULL.
+Calling with a non-positive xNum.  Should generate error, return NULL.
+Calling with a non-positive yNum.  Should generate error, return NULL.
+Calling with a non-positive border.  Should generate error, return NULL.
+Calling with acceptable input parameters, non-NULL mask.
+Calling with acceptable input parameters, NULL mask.
+Testing pmSubtractionFindStamps(100, 100, 10, 10, 2)
+Calling with a NULL psImage.  Should generate error, return NULL.
+Calling with a non-positive xNum.  Should generate error, return NULL.
+Calling with a non-positive yNum.  Should generate error, return NULL.
+Calling with a non-positive border.  Should generate error, return NULL.
+Calling with acceptable input parameters, non-NULL mask.
+Calling with acceptable input parameters, NULL mask.
+Testing pmSubtractionCalculateEquation(): 
+    image size is (25, 25)
+    num stamps is (2, 2).  Border is 2
+   kernel type is PM_SUBTRACTION_KERNEL_POIS.
+Generating stamps...
+Generating kernel basis functions...
+Calling with a NULL psArray stamps.  Should generate error, return FALSE.
+Calling with a NULL reference images.  Should generate error, return FALSE.
+Calling with a NULL input images.  Should generate error, return FALSE.
+Calling with a NULL kernel basis functions.  Should generate error, return FALSE.
+Calling with acceptable input parameters.  Should return TRUE.
+Calling pmSubtractionSolveEquation() with a NULL stamp argument.  Should generate error, return FALSE.
+Calling pmSubtractionSolveEquation() with acceptable input parameters.  Should return non-NULL.
+The solution vector is:
+(-3866492.76) (-520094.85) (-1277345.36) (-481569.41) (0.21) (-1182727.56) (0.01) (0.01) (-0.00) (-0.02) (-0.00) (0.00) (-0.01) (0.01) (-0.00) (-0.01) (0.00) (0.00) (0.02) (-0.01) (-0.01) (0.00) (0.01) (0.01) (-0.03) (0.01) (0.00) (-0.01) (0.00) (0.00) (0.01) (-0.01) (-0.02) (0.00) (0.01) (0.01) (-0.01) (0.01) (-0.00) (-0.01) (0.00) (0.01) (-0.02) (0.01) (0.02) (-0.01) (-0.00) (-0.01) (0.04) (-0.02) (-0.02) (0.01) (0.02) (0.01) (-0.03) (0.03) (0.02) (-0.02) (-0.01) (-0.01) (-0.01) (0.01) (0.00) (-0.01) (0.00) (0.00) (-0.02) (0.01) (0.01) (-0.01) (0.00) (-0.00) (-0.01) (-0.02) (0.01) (0.02) (0.02) (-0.01) (0.04) (0.00) (-0.01) (-0.01) (-0.02) (-0.00) (0.00) (-0.00) (-0.01) (0.00) (0.01) (0.01) (0.00) (-0.01) (-0.02) (0.02) (0.02) (0.02) (0.01) (-0.01) (-0.02) (0.01) (0.01) (0.02) (0.03) (-0.00) (-0.05) (-0.00) (0.01) (0.05) (-0.04) (0.03) (0.03) (-0.03) (-0.04) (-0.02) (-0.02) (-0.00) (0.00) (0.01) (0.00) (0.00) (-0.02) (0.01) (0.01) (-0.00) (-0.00) (-0.01) (-0.03) (0.02) (0.01) (-0.02) (-0.01) (-0.00) (0.01) (-0.01) (-0.02) (0.01) (0.00) (0.02) (-0.03) (0.01) (0.00) (-0.00) (-0.00) (0.00) (0.00) (-0.02) (-0.02) (0.02) (0.01) (0.02) (0.49) 
+Calling pmSubtractionRejectStamps() with acceptable input parameters.  Should return TRUE.
+Calling pmSubtractionKernelImage() with NULL solution.  Should generate error, return NULL.
+Calling pmSubtractionKernelImage() with NULL kernels.  Should generate error, return NULL.
+Calling pmSubtractionKernelImage() unallowable x value.  Should generate error, return NULL.
+Calling pmSubtractionKernelImage() unallowable x value.  Should generate error, return NULL.
+Calling pmSubtractionKernelImage() unallowable y value.  Should generate error, return NULL.
+Calling pmSubtractionKernelImage() unallowable y value.  Should generate error, return NULL.
+Calling pmSubtractionKernelImage() with acceptable input parameters.  Should return a psImage.
+-0.001170 -0.025330 0.003882 -0.024851 -0.014656 
+-0.019844 -0.032570 0.014897 0.002807 0.000729 
+0.002328 0.031599 -5181287.500000 -0.004433 -0.013596 
+-0.008172 -0.016571 0.033990 -0.012052 -0.002317 
+0.010558 -0.020875 0.011541 -0.006648 0.006954 
+Testing pmSubtractionCalculateEquation(): 
+    image size is (25, 25)
+    num stamps is (2, 2).  Border is 2
+   kernel type is PM_SUBTRACTION_KERNEL_ISIS.
+Generating stamps...
+Generating kernel basis functions...
+Calling with a NULL psArray stamps.  Should generate error, return FALSE.
+Calling with a NULL reference images.  Should generate error, return FALSE.
+Calling with a NULL input images.  Should generate error, return FALSE.
+Calling with a NULL kernel basis functions.  Should generate error, return FALSE.
+Calling with acceptable input parameters.  Should return TRUE.
+Calling pmSubtractionSolveEquation() with a NULL stamp argument.  Should generate error, return FALSE.
+Calling pmSubtractionSolveEquation() with acceptable input parameters.  Should return non-NULL.
+The solution vector is:
+(0.09) (-0.01) (-0.01) (0.00) (-0.00) (-0.00) (0.00) (-0.00) (0.00) (0.00) (-0.00) (0.00) (0.00) (-0.00) (0.00) (0.00) (-0.00) (0.00) (0.00) (-0.00) (0.00) (0.00) (-0.00) (0.00) (0.00) (-0.00) (0.00) (0.00) (-0.00) (0.00) (0.00) (-0.00) (0.00) (0.00) (-0.00) (0.00) (0.55) 
+Calling pmSubtractionRejectStamps() with acceptable input parameters.  Should return TRUE.
+Calling pmSubtractionKernelImage() with NULL solution.  Should generate error, return NULL.
+Calling pmSubtractionKernelImage() with NULL kernels.  Should generate error, return NULL.
+Calling pmSubtractionKernelImage() unallowable x value.  Should generate error, return NULL.
+Calling pmSubtractionKernelImage() unallowable x value.  Should generate error, return NULL.
+Calling pmSubtractionKernelImage() unallowable y value.  Should generate error, return NULL.
+Calling pmSubtractionKernelImage() unallowable y value.  Should generate error, return NULL.
+Calling pmSubtractionKernelImage() with acceptable input parameters.  Should return a psImage.
+0.031614 0.045997 0.052122 0.045997 0.031614 
+0.045997 0.066926 0.075837 0.066926 0.045997 
+0.052122 0.075837 0.085934 0.075837 0.052122 
+0.045997 0.066926 0.075837 0.066926 0.045997 
+0.031614 0.045997 0.052122 0.045997 0.031614 
Index: /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmSubtractBias.stderr
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmSubtractBias.stderr	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmSubtractBias.stderr	(revision 6961)
@@ -0,0 +1,9 @@
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: Test Point Driver{pmSubtractBias}                          *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (Test Point Driver{pmSubtractBias} | tst_pmSubtractBias.c)
+
Index: /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmSubtractBias.stdout
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmSubtractBias.stdout	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmSubtractBias.stdout	(revision 6961)
@@ -0,0 +1,672 @@
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 6 by 6
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_ALL
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 6 by 6
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_COLUMNS
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 6 by 6
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_ROWS
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 6 by 6
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_ALL
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 6 by 6
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_COLUMNS
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 6 by 6
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_ROWS
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_ALL
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_COLUMNS
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_ROWS
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_ALL
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_COLUMNS
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_ROWS
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 10 by 10
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_ALL
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 10 by 10
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_COLUMNS
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 10 by 10
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_ROWS
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 10 by 10
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_ALL
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 10 by 10
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_COLUMNS
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 10 by 10
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_ROWS
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 1
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_ALL
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 1
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_COLUMNS
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 1
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_ROWS
+    Fit type: PM_FIT_SPLINE
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 6 by 6
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_ALL
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 6 by 6
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_COLUMNS
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 6 by 6
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_ROWS
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 6 by 6
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_ALL
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 6 by 6
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_COLUMNS
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 6 by 6
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_ROWS
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_ALL
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_COLUMNS
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_ROWS
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_ALL
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_COLUMNS
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_ROWS
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 10 by 10
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_ALL
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 10 by 10
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_COLUMNS
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 10 by 10
+    Total Overscans: 2
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_ROWS
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 10 by 10
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_ALL
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 10 by 10
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_COLUMNS
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 10 by 10
+    Total Overscans: 2
+    Binning factor: 2
+    Overscan axis: PM_OVERSCAN_ROWS
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 1
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_ALL
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 1
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_COLUMNS
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractBias.c                                       *
+*            TestPoint: pmSubtractBias{PUT COMMENT HERE}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+---- doSubtractOverscansGeneric() ----
+    Image size: 8 by 8
+    Overscan size: 8 by 8
+    Total Overscans: 1
+    Binning factor: 1
+    Overscan axis: PM_OVERSCAN_ROWS
+    Fit type: PM_FIT_POLYNOMIAL
+
+---> TESTPOINT PASSED (pmSubtractBias{Column Overscans} | tst_pmSubtractBias.c)
+
Index: /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmSubtractSky.stderr
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmSubtractSky.stderr	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmSubtractSky.stderr	(revision 6961)
@@ -0,0 +1,162 @@
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: Test Point Driver{pmSubtractSky}                           *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|p_psVectorSampleStdev
+    WARNING: p_psVectorSampleStdev(): only one valid psVector elements (1).  Setting stats->sampleStdev = 0.0.
+<HOST>|W|ImageFitPolynomial
+    WARNING: ImageFitPolynomial(): Reducing polynomial complexity in x-dimension.
+<HOST>|W|ImageFitPolynomial
+    WARNING: ImageFitPolynomial(): Reducing polynomial complexity in y-dimension.
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|ImageFitPolynomial
+    WARNING: ImageFitPolynomial(): Reducing polynomial complexity in x-dimension.
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|ImageFitPolynomial
+    WARNING: ImageFitPolynomial(): Reducing polynomial complexity in y-dimension.
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|p_psVectorSampleStdev
+    WARNING: p_psVectorSampleStdev(): only one valid psVector elements (1).  Setting stats->sampleStdev = 0.0.
+<HOST>|W|ImageFitPolynomial
+    WARNING: ImageFitPolynomial(): Reducing polynomial complexity in x-dimension.
+<HOST>|W|ImageFitPolynomial
+    WARNING: ImageFitPolynomial(): Reducing polynomial complexity in y-dimension.
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|ImageFitPolynomial
+    WARNING: ImageFitPolynomial(): Reducing polynomial complexity in x-dimension.
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|ImageFitPolynomial
+    WARNING: ImageFitPolynomial(): Reducing polynomial complexity in y-dimension.
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|ImageFitPolynomial
+    WARNING: ImageFitPolynomial(): Reducing polynomial complexity in x-dimension.
+<HOST>|W|ImageFitPolynomial
+    WARNING: ImageFitPolynomial(): Reducing polynomial complexity in y-dimension.
+
+---> TESTPOINT PASSED (Test Point Driver{pmSubtractSky} | tst_pmSubtractSky.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: Test Point Driver{pmSubtractSky: warning, error messages}  *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+<HOST>|E|pmSubtractSky (FILE:LINENO)
+    Unallowable operation: psReadout in or its data is NULL.
+<HOST>|E|pmSubtractSky (FILE:LINENO)
+    Unallowable operation: psReadout in or its data is NULL.
+<HOST>|E|pmSubtractSky (FILE:LINENO)
+    Unallowable operation: psImage in has incorrect type.
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmSubtractSky
+    WARNING: pmSubtractSky(): input parameter stats is NULL
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmSubtractSky
+    WARNING: pmSubtractSky(): no stats->options was requested
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmSubtractSky
+    WARNING: Multiple statistical options have been requested.
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmSubtractSky
+    WARNING: pmSubtractSky(): binFactor is 0
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmSubtractSky
+    WARNING: pmSubtractSky(): binFactor is -1
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|W|p_psDetermineTrimmedImage
+    WARNING: could not determine CELL.TRIMSEC from parent cell Metadata (NULL).
+<HOST>|W|pmSubtractSky
+    WARNING: pmSubtractSky(): clipSD is -1.000000
+<HOST>|W|pmSubtractSky
+    WARNING: in->parent is NULL.
+<HOST>|E|pmSubtractSky (FILE:LINENO)
+    psFit is unallowable (54321).  Returning in image.
+
+---> TESTPOINT PASSED (Test Point Driver{pmSubtractSky: warning, error messages} | tst_pmSubtractSky.c)
+
Index: /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmSubtractSky.stdout
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmSubtractSky.stdout	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/imsubtract/verified/tst_pmSubtractSky.stdout	(revision 6961)
@@ -0,0 +1,219 @@
+doSubtractSkySimple(1, 1, 1)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkySimple}                         *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkySimple} | tst_pmSubtractSky.c)
+
+doSubtractSkySimple(NUM_COLS, 1, 1)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkySimple}                         *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkySimple} | tst_pmSubtractSky.c)
+
+doSubtractSkySimple(1, NUM_ROWS, 1)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkySimple}                         *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkySimple} | tst_pmSubtractSky.c)
+
+doSubtractSkySimple(NUM_COLS, NUM_ROWS, 1)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkySimple}                         *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkySimple} | tst_pmSubtractSky.c)
+
+doSubtractSkySimple(1, 1, 2)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkySimple}                         *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkySimple} | tst_pmSubtractSky.c)
+
+doSubtractSkySimple(NUM_COLS, 1, 2)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkySimple}                         *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkySimple} | tst_pmSubtractSky.c)
+
+doSubtractSkySimple(1, NUM_ROWS, 2)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkySimple}                         *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkySimple} | tst_pmSubtractSky.c)
+
+doSubtractSkySimple(NUM_COLS, NUM_ROWS, 2)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkySimple}                         *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkySimple} | tst_pmSubtractSky.c)
+
+doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 1)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkyWithObjects}                    *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkyWithObjects} | tst_pmSubtractSky.c)
+
+doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 2)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkyWithObjects}                    *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkyWithObjects} | tst_pmSubtractSky.c)
+
+doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 4)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkyWithObjects}                    *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkyWithObjects} | tst_pmSubtractSky.c)
+
+doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 8)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkyWithObjects}                    *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkyWithObjects} | tst_pmSubtractSky.c)
+
+doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 16)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkyWithObjects}                    *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkyWithObjects} | tst_pmSubtractSky.c)
+
+doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 32)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkyWithObjects}                    *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkyWithObjects} | tst_pmSubtractSky.c)
+
+doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 64)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkyWithObjects}                    *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkyWithObjects} | tst_pmSubtractSky.c)
+
+doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 128)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkyWithObjects}                    *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkyWithObjects} | tst_pmSubtractSky.c)
+
+doSubtractSkyWithObjects(NUM_COLS, NUM_ROWS, 256)
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{doSubtractSkyWithObjects}                    *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+
+---> TESTPOINT PASSED (pmSubtractSky{doSubtractSkyWithObjects} | tst_pmSubtractSky.c)
+
+/***************************** TESTPOINT ******************************************\
+*             TestFile: tst_pmSubtractSky.c                                        *
+*            TestPoint: pmSubtractSky{Testing bad input parameter conditions.}     *
+*             TestType: Positive                                                   *
+\**********************************************************************************/
+
+----------------------------------------------------------------
+Calling pmSubtractSky() with NULL pmReadout.  Should error.
+
+----------------------------------------------------------------
+Calling pmSubtractSky() with NULL pmReadout->image.  Should error.
+
+----------------------------------------------------------------
+Calling pmSubtractSky() with PS_TYPE_F64 pmReadout->image.  Should error.
+
+----------------------------------------------------------------
+Calling pmSubtractSky() with NULL fitSpec.  Should return image, no ERROR, no WARNING.
+
+----------------------------------------------------------------
+Calling pmSubtractSky() with PM_FIT_NONE fit.  Should return image, no ERROR, no WARNING.
+
+----------------------------------------------------------------
+Calling pmSubtractSky() with PM_FIT_SPLINE fit.  Should return image, no ERROR, no WARNING.
+
+----------------------------------------------------------------
+Calling pmSubtractSky() with NULL myStats.  Should fit entire image, generate WARNING.
+
+----------------------------------------------------------------
+Calling pmSubtractSky() with no myStats->options specified.  Should fit entire image, generate WARNING.
+
+----------------------------------------------------------------
+Calling pmSubtractSky() with multiple myStats->options specified.  Should fit entire image, generate WARNING.
+
+----------------------------------------------------------------
+Calling pmSubtractSky() with 0 binFactor.  Should fit entire image, generate WARNING.
+
+----------------------------------------------------------------
+Calling pmSubtractSky() with -1 binFactor.  Should fit entire image, generate WARNING.
+
+----------------------------------------------------------------
+Calling pmSubtractSky() with -1.0 clipSD.  Should fit entire image, generate WARNING.
+
+----------------------------------------------------------------
+Calling pmSubtractSky() with bogus psFit.  Should generate Error.
+
+----------------------------------------------------------------
+
+---> TESTPOINT PASSED (pmSubtractSky{Testing bad input parameter conditions.} | tst_pmSubtractSky.c)
+
Index: /tags/rel11_ifa_pre0/psModules/test/objects/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/objects/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/objects/.cvsignore	(revision 6961)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+tst_pmObjects01
Index: /tags/rel11_ifa_pre0/psModules/test/objects/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/objects/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/objects/Makefile.am	(revision 6961)
@@ -0,0 +1,21 @@
+# Makefile for psModule tests
+
+AM_LDFLAGS = -L$(top_builddir)/src -lpsmodule $(PSMODULE_LIBS)
+AM_CFLAGS  = @AM_CFLAGS@ $(PSMODULE_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/rel11_ifa_pre0/psModules/test/objects/tst_pmObjects01.c
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/objects/tst_pmObjects01.c	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/objects/tst_pmObjects01.c	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/objects/verified/tst_pmObjects01.stderr
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/objects/verified/tst_pmObjects01.stderr	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/objects/verified/tst_pmObjects01.stderr	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/objects/verified/tst_pmObjects01.stdout
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/objects/verified/tst_pmObjects01.stdout	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/objects/verified/tst_pmObjects01.stdout	(revision 6961)
@@ -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/rel11_ifa_pre0/psModules/test/photom/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/photom/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/photom/.cvsignore	(revision 6961)
@@ -0,0 +1,6 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
+
Index: /tags/rel11_ifa_pre0/psModules/test/photom/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/photom/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/photom/Makefile.am	(revision 6961)
@@ -0,0 +1,16 @@
+# Makefile for psModule tests
+
+AM_LDFLAGS = -L$(top_builddir)/src -lpsmodule $(PSMODULE_LIBS)
+AM_CFLAGS  = @AM_CFLAGS@ $(PSMODULE_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/rel11_ifa_pre0/psModules/test/pslib/.cvsignore
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/pslib/.cvsignore	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/pslib/.cvsignore	(revision 6961)
@@ -0,0 +1,5 @@
+.deps
+.libs
+Makefile
+Makefile.in
+temp
Index: /tags/rel11_ifa_pre0/psModules/test/pslib/Makefile.am
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/pslib/Makefile.am	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/pslib/Makefile.am	(revision 6961)
@@ -0,0 +1,16 @@
+# Makefile for psModule tests
+
+AM_LDFLAGS = -L$(top_builddir)/src -lpsmodule $(PSMODULE_LIBS)
+AM_CFLAGS  = @AM_CFLAGS@ $(PSMODULE_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/rel11_ifa_pre0/psModules/test/runTest
===================================================================
--- /tags/rel11_ifa_pre0/psModules/test/runTest	(revision 6961)
+++ /tags/rel11_ifa_pre0/psModules/test/runTest	(revision 6961)
@@ -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);
+}
+
