Index: /branches/eam_branches/eam_branch_20090312/ppImage/Doxyfile.in
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/Doxyfile.in	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/Doxyfile.in	(revision 23312)
@@ -0,0 +1,1310 @@
+# Doxyfile 1.5.4
+
+# This file describes the settings to be used by the documentation system
+# doxygen (www.doxygen.org) for a project
+#
+# All text after a hash (#) is considered a comment and will be ignored
+# The format is:
+#       TAG = value [value, ...]
+# For lists items can also be appended using:
+#       TAG += value [value, ...]
+# Values that contain spaces should be placed between quotes (" ")
+
+#---------------------------------------------------------------------------
+# Project related configuration options
+#---------------------------------------------------------------------------
+
+# This tag specifies the encoding used for all characters in the config file that 
+# follow. The default is UTF-8 which is also the encoding used for all text before 
+# the first occurrence of this tag. Doxygen uses libiconv (or the iconv built into 
+# libc) for the transcoding. See http://www.gnu.org/software/libiconv for the list of 
+# possible encodings.
+
+DOXYFILE_ENCODING      = UTF-8
+
+# The PROJECT_NAME tag is a single word (or a sequence of words surrounded 
+# by quotes) that should identify the project.
+
+PROJECT_NAME           = ppImage
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER         = ipp-2.7.dev
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = ./docs
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of 
+# source files, where putting all generated files in the same directory would 
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, 
+# Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hungarian, 
+# Italian, Japanese, Japanese-en (Japanese with English messages), Korean, 
+# Korean-en, Lithuanian, Norwegian, Polish, Portuguese, Romanian, Russian, 
+# Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is 
+# used as the annotated text. Otherwise, the brief description is used as-is. 
+# If left blank, the following values are used ("$name" is automatically 
+# replaced with the name of the entity): "The $name class" "The $name widget" 
+# "The $name file" "is" "provides" "specifies" "contains" 
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
+# inherited members of a class in the documentation of that class as if those 
+# members were ordinary class members. Constructors, destructors and assignment 
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful is your file systems 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like regular Qt-style comments 
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will 
+# interpret the first line (until the first dot) of a Qt-style 
+# comment as the brief description. If set to NO, the comments 
+# will behave just like regular Qt-style comments (thus requiring 
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen 
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member 
+# documentation.
+
+DETAILS_AT_TOP         = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
+# a new page for each member. If set to NO, the documentation of a member will 
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
+# sources only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java 
+# sources only. Doxygen will then generate output that is more tailored for Java. 
+# For instance, namespaces will be presented as packages, qualified scopes 
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to 
+# include (a tag file for) the STL sources as input, then you should 
+# set this tag to YES in order to let doxygen match functions declarations and 
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. 
+# func(std::string) {}). This also make the inheritance and collaboration 
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. 
+# Doxygen will parse them like normal C++ but will assume all classes use public 
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct (or union) is 
+# documented as struct with the name of the typedef. So 
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct 
+# with name TypeT. When disabled the typedef will appear as a member of a file, 
+# namespace, or class. And the struct will be named TypeS. This can typically 
+# be useful for C code where the coding convention is that all structs are 
+# typedef'ed and only the typedef is referenced never the struct's name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local 
+# methods, which are defined in the implementation section but not in 
+# the interface are included in the documentation. 
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be extracted 
+# and appear in the documentation as a namespace called 'anonymous_namespace{file}', 
+# where file will be replaced with the base name of the file that contains the anonymous 
+# namespace. By default anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories 
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy 
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
+# doxygen should invoke to get the current version for each file (typically from the 
+# version control system). Doxygen will invoke the program by executing (via 
+# popen()) the command <command> <input-file>, where <command> is the value of 
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
+# provided by doxygen. Whatever the program writes to standard output 
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for 
+# functions that are documented, but have no documentation for their parameters 
+# or return value. If set to NO (the default) doxygen will only warn about 
+# wrong or incomplete parameter documentation, but not about the absence of 
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text. Optionally the format may contain 
+# $version, which will be replaced by the version of the file (if it could 
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text "
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT                  = ./src
+
+# This tag can be used to specify the character encoding of the source files that 
+# doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default 
+# input encoding. Doxygen uses libiconv (or the iconv built into libc) for the transcoding. 
+# See http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx 
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS          = *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                = 
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
+# directories that are symbolic links (a Unix filesystem feature) are excluded 
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories. Note that the wildcards are matched 
+# against the file with absolute path, so to exclude all test directories 
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       = 
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names 
+# (namespaces, classes, functions, etc.) that should be excluded from the output. 
+# The symbol name can be a fully qualified name, a word, or if the wildcard * is used, 
+# a substring. Examples: ANamespace, AClass, AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           = 
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = 
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis.  Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match.  The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
+# is applied to all files.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO. If you have enabled CALL_GRAPH or CALLER_GRAPH 
+# then you must also enable this option. If you don't then doxygen will produce 
+# a warning and turn it on anyway
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default) 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default) 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.  Otherwise they will link to the documentstion.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code 
+# will point to the HTML generated by the htags(1) tool instead of doxygen 
+# built-in source browser. The htags tool is part of GNU's global source 
+# tagging system (see http://www.gnu.org/software/global/global.html). You 
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        = 
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
+# files or namespaces will be aligned in HTML using tables. If set to 
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML 
+# documentation will contain sections that can be hidden and shown after the 
+# page has loaded. For this to work a browser that supports 
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox 
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA             = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD                = 
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader.  This is useful 
+# if you want to understand what is going on.  On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor   
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED             = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse 
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#   TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#   TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
+# or super classes. Setting the tag to NO turns the diagrams off. Note that 
+# this option is superseded by the HAVE_DOT option below. This is only a 
+# fallback. It is recommended to install and use dot, since it yields more 
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc 
+# command. Doxygen will then run the mscgen tool (see http://www.mcternan.me.uk/mscgen/) to 
+# produce the chart and insert it in the documentation. The MSCGEN_PATH tag allows you to 
+# specify the directory where the mscgen tool resides. If left empty the tool is assumed to 
+# be found in the default search path.
+
+MSCGEN_PATH            = 
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will 
+# generate a call dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable call graphs for selected 
+# functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will 
+# generate a caller dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable caller graphs for selected 
+# functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES 
+# then doxygen will show the dependencies a directory has on other directories 
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of 
+# nodes that will be shown in the graph. If the number of nodes in a graph 
+# becomes larger than this value, doxygen will truncate the graph, which is 
+# visualized by representing a node as a red box. Note that doxygen if the number 
+# of direct children of the root node in a graph is already larger than 
+# MAX_DOT_GRAPH_NOTES then the graph will not be shown at all. Also note 
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes 
+# that lay further from the root node will be omitted. Note that setting this 
+# option to 1 or 2 may greatly reduce the computation time needed for large 
+# code bases. Also note that the size of a graph can be further restricted by 
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
+# background. This is disabled by default, which results in a white background. 
+# Warning: Depending on the platform used, enabling this option may lead to 
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to 
+# read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
+# files in one run (i.e. multiple -o and -T options on the command line). This 
+# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine   
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
Index: /branches/eam_branches/eam_branch_20090312/ppImage/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/Makefile.am	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/Makefile.am	(revision 23312)
@@ -0,0 +1,3 @@
+SUBDIRS = src
+
+CLEANFILES = *~ core core.*
Index: /branches/eam_branches/eam_branch_20090312/ppImage/autogen.sh
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/autogen.sh	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/autogen.sh	(revision 23312)
@@ -0,0 +1,103 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+srcdir=`dirname $0`
+test -z "$srcdir" && srcdir=.
+
+ORIGDIR=`pwd`
+cd $srcdir
+
+PROJECT=ppImage
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOLIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOLIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/eam_branches/eam_branch_20090312/ppImage/configure.ac
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/configure.ac	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/configure.ac	(revision 23312)
@@ -0,0 +1,36 @@
+AC_PREREQ(2.61)
+
+AC_INIT([ppImage], [1.1.0], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([src])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+IPP_STDCFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC_C99
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE
+
+PKG_CHECK_MODULES([PSLIB],    [pslib >= 1.0.0])
+PKG_CHECK_MODULES([PSMODULE], [psmodules >= 1.0.0])
+PKG_CHECK_MODULES([PSPHOT],   [psphot >= 0.8.0]) 
+PKG_CHECK_MODULES([PSASTRO],  [psastro >= 0.8.0]) 
+PKG_CHECK_MODULES([PPSTATS],  [ppStats >= 1.0.0]) 
+
+IPP_STDOPTS
+CFLAGS="${CFLAGS=} -Wall -Werror"
+
+AC_SUBST([PPIMAGE_CFLAGS])
+AC_SUBST([PPIMAGE_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  Doxyfile
+])
+AC_OUTPUT
Index: /branches/eam_branches/eam_branch_20090312/ppImage/notes.txt
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/notes.txt	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/notes.txt	(revision 23312)
@@ -0,0 +1,23 @@
+
+ppImage pmFPAfiles:
+
+    pmFPAfile *input = pmFPAfileDefineFromArgs (&status, config, "PPIMAGE.INPUT", "INPUT");
+    pmFPAfile *inputMask = pmFPAfileBindFromArgs(&status, input, config, "PPIMAGE.INPUT.MASK", "INPUT.MASK");
+    pmFPAfile *inputWeight = pmFPAfileBindFromArgs(&status, input, config, "PPIMAGE.INPUT.WEIGHT", "INPUT.WEIGHT");
+    pmFPAfile *output = pmFPAfileDefineOutput(config, input->fpa, "PPIMAGE.OUTPUT");
+    pmFPAfile *outMask = pmFPAfileDefineOutput(config, input->fpa, "PPIMAGE.OUTPUT.MASK");
+    pmFPAfile *outWeight = pmFPAfileDefineOutput(config, input->fpa, "PPIMAGE.OUTPUT.WEIGHT");
+    pmFPAfile *chipImage = pmFPAfileDefineChipMosaic(config, input->fpa, "PPIMAGE.CHIP");
+    pmFPAfile *chipMask = pmFPAfileDefineOutput(config, chipImage->fpa, "PPIMAGE.CHIP.MASK");
+    pmFPAfile *chipWeight = pmFPAfileDefineOutput(config, chipImage->fpa, "PPIMAGE.CHIP.WEIGHT");
+    pmFPAfile *byFPA1 = pmFPAfileDefineFPAMosaic(config, input->fpa, "PPIMAGE.OUTPUT.FPA1");
+    pmFPAfile *byFPA2 = pmFPAfileDefineFPAMosaic(config, input->fpa, "PPIMAGE.OUTPUT.FPA2");
+
+        pmFPAfile *psphotInput = pmFPAfileDefineFromFPA (config, chipImage->fpa, 1, 1, "PSPHOT.INPUT");
+        pmFPAfile *psphotOutput = psMetadataLookupPtr (&status, config->files, "PSPHOT.OUTPUT");
+        pmFPAfile *psastroInput = pmFPAfileDefineInput (config, psphotOutput->fpa, "PSASTRO.INPUT");
+
+    pmFPAfile *bin1 = pmFPAfileDefineFromFPA (config, chipImage->fpa, options->xBin1, options->yBin1, "PPIMAGE.BIN1");
+    pmFPAfile *bin2 = pmFPAfileDefineFromFPA (config, chipImage->fpa, options->xBin2, options->yBin2, "PPIMAGE.BIN2");
+    pmFPAfile *jpg1 = pmFPAfileDefineOutput (config, byFPA1->fpa, "PPIMAGE.JPEG1");
+    pmFPAfile *jpg2 = pmFPAfileDefineOutput (config, byFPA2->fpa, "PPIMAGE.JPEG2");
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/Makefile.am	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/Makefile.am	(revision 23312)
@@ -0,0 +1,50 @@
+bin_PROGRAMS = ppImage
+
+noinst_HEADERS = \
+	ppImage.h 
+
+#PPIMAGE_VERSION=`if [ -e ../../VERSION ]; then cat ../../VERSION; else svnversion; fi`
+#PPIMAGE_BRANCH=`if [ -e ../../BRANCH ]; then cat ../../BRANCH; else svn info | sed -n -e '/URL:/ h' -e '/Repository Root:/ { x; H; x; s|Repository Root: \(.*\)\nURL: \1\(.*\)|\2| ; s|^/|| ; s|/[a-zA-Z]*/src.*|| ; p }'; fi`
+#PPIMAGE_SOURCE=`if [ -e ../../SOURCE ]; then cat ../../SOURCE; else svn info | sed -n -e 's/Repository UUID: // p'; fi`
+
+# Force recompilation of ppImageVersion.c, since it gets the version information
+# ppImageVersion.c: FORCE
+# 	touch ppImageVersion.c
+# FORCE: ;
+
+ppImage_CFLAGS = $(PPIMAGE_CFLAGS) $(PPSTATS_CFLAGS) $(PSASTRO_CFLAGS) $(PSPHOT_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS) -DPPIMAGE_VERSION=$(SVN_VERSION) -DPPIMAGE_BRANCH=$(SVN_BRANCH) -DPPIMAGE_SOURCE=$(SVN_SOURCE)
+ppImage_LDFLAGS = $(PPIMAGE_LIBS) $(PSASTRO_LIBS) $(PPSTATS_LIBS) $(PSPHOT_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+ppImage_SOURCES = \
+	ppImage.c \
+	ppImageArguments.c \
+	ppImageParseCamera.c \
+	ppImageLoop.c \
+	ppImageCleanup.c \
+	ppImageOptions.c \
+	ppImageDetrendReadout.c \
+	ppImageDetrendRecord.c \
+	ppImageDetrendNonLinear.c \
+	ppImageDetrendFringe.c \
+	ppImageDetrendFree.c \
+	ppImageRebinReadout.c \
+	ppImageMosaic.c \
+	ppImagePhotom.c \
+	ppImageAstrom.c \
+	ppImageAddstar.c \
+	ppImageStatsOutput.c \
+	ppImagePixelStats.c \
+	ppImageMetadataStats.c \
+	ppImageReplaceBackground.c \
+	ppImageDefineFile.c \
+	ppImageSetMaskBits.c \
+	ppImageFileCheck.c \
+	ppImageVersion.c \
+	ppImageMemory.c
+
+CLEANFILES = *~
+
+clean-local:
+	-rm -f TAGS
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppFocus.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppFocus.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppFocus.c	(revision 23312)
@@ -0,0 +1,74 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+int main(int argc, char **argv) {
+
+    psLibInit(NULL);
+
+    psTimerStart(TIMER_TOTAL);
+
+    // Parse the configuration and arguments
+    // Open the input image(s)
+    // Determine camera, format from header if not already defined
+    // Construct camera in preparation for reading
+    pmConfig *config = ppFocusArguments(argc, argv);
+    if (config == NULL) {
+        psErrorStackPrint(stderr, " ");
+        exit(1);
+    }
+
+    // we search the argument data for the named fileset (argname)
+    psArray *infiles = psMetadataLookupPtr(NULL, config->arguments, "INPUT");
+    if (!infiles) {
+        psTrace("pmFPAfile", 5, "Failed to find INPUT in argument list");
+        exit(1);
+    }
+
+    // allocate vectors for analysis
+    psVector *focus = psVectorAllocEmpty(infiles->n, PS_TYPE_F32);
+    psVector *fwhm = psVectorAllocEmpty(infiles->n, PS_TYPE_F32);
+
+    for (int i = 0; i < infiles->n; i++) {
+
+        // define recipe options
+        // define the active I/O files
+        ppImageOptions *options = ppFocusParseCamera(config, i);
+        if (options == NULL) {
+            psErrorStackPrint(stderr, " ");
+            exit(1);
+        }
+
+        // Image Arithmetic Loop
+        // XXX ppFocus REQUIRES photom: for it to be true?
+        //
+        if (!ppImageLoop(config, options)) {
+            psErrorStackPrint(stderr, " ");
+            exit(1);
+        }
+
+        // determine FWHM at reference location in image
+        // (also removes PPIMAGE.INPUT from config->files)
+        ppFocusGetFWHM (config, focus, fwhm);
+
+        ppFocusDropCamera (config);
+        psFree (options);
+    }
+
+    ppFocusFitFWHM (config, focus, fwhm);
+
+    psLogMsg ("ppFocus", 3, "complete ppFocus run: %f sec\n", psTimerMark (TIMER_TOTAL));
+
+    // Cleaning up
+    psFree (focus);
+    psFree (fwhm);
+    ppImageCleanup(config, NULL);
+    return EXIT_SUCCESS;
+}
+
+// ppFocus is a lot like ppImage, but with a few important differences:
+// - the input list is a set of independent images (not multiple files for a single image)
+// - each pass to ppImageLoop performs the analysis on a different pmFPAfile
+// - after each ppImageLoop, grap the input pmFPAfile and extract the FWHM stats
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppFocusArguments.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppFocusArguments.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppFocusArguments.c	(revision 23312)
@@ -0,0 +1,72 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+static void usage (void) {
+    fprintf (stderr, "USAGE: ppFocus [-file focus.*.fits] [-list INPUT.txt] OUTPUT\n");
+    exit (2);
+}
+
+pmConfig *ppFocusArguments(int argc, char **argv) {
+
+    int N;
+    bool status;
+
+    if (argc == 1) usage ();
+
+    // load the site-wide configuration information
+    pmConfig *config = pmConfigRead(&argc, argv, RECIPE_NAME);
+    if (config == NULL) {
+        psErrorStackPrint(stderr, "Can't find site configuration!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PPIMAGE recipe values loaded from recipe files
+    psMetadata *options = pmConfigRecipeOptions (config, RECIPE_NAME);
+
+    // save these recipe options until we have loaded the options
+    // psMetadata *options = psMetadataAlloc ();
+    // psMetadataAddPtr (config->arguments, PS_LIST_TAIL, "PPIMAGE.OPTIONS",  PS_DATA_METADATA, "", options);
+
+    // the following options override the PPIMAGE recipe options
+
+    // recipe option: -usemask : override MASK setting in phase2.recipe
+    if ((N = psArgumentGet(argc, argv, "-usemask"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddBool (options, PS_LIST_TAIL, "MASK", PS_META_REPLACE, "", true);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // XXX add other PPIMAGE recipe options here
+
+    // the input file is a required argument; if not found, we will exit
+    status = pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT", "-file", "-list");
+    if (!status) { usage ();}
+
+    // if these command-line options are supplied, load the file name lists into config->arguments
+    // override any configuration-specified source for these files
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "BIAS", "-bias", "-biaslist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "DARK", "-dark", "-darklist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "FLAT", "-flat", "-flatlist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "MASK", "-mask", "-masklist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "FRINGE", "-fringe", "-fringelist");
+
+    // chip selection is used to limit chips to be processed
+    if ((N = psArgumentGet (argc, argv, "-chip"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (config->arguments, PS_LIST_TAIL, "CHIP_SELECTIONS", PS_DATA_STRING, "",
+                          argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    if (argc != 2) usage ();
+
+    // Add the input and output images (which remain on the command-line) to the arguments list
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "Name of the output image",
+                     argv[1]);
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppFocusFitFWHM.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppFocusFitFWHM.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppFocusFitFWHM.c	(revision 23312)
@@ -0,0 +1,30 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+bool ppFocusFitFWHM (pmConfig *config, psVector *focus, psVector *fwhm) {
+
+    float minFocus;
+
+    psPolynomial1D *poly = psPolynomial1DAlloc (PS_POLYNOMIAL_ORD, 2);
+
+    if (!psVectorFitPolynomial1D (poly, NULL, 0, fwhm, NULL, focus)) {
+	psError (PS_ERR_UNKNOWN, false, "failed to fit focus/fwhm trend; invalid image data?");
+	return false;
+    }
+
+    if (poly->coeff[2] <= 0.0) {
+	psLogMsg ("ppFocus", 3, "poor focus fit: zero or negative curvature\n");
+	psLogMsg ("ppFocus", 3, "fit coeffs: %f  %f  %f\n", 
+		  poly->coeff[0], poly->coeff[1], poly->coeff[2]);
+    }
+    
+    minFocus = -0.5 * poly->coeff[1] / poly->coeff[2];
+    psLogMsg ("ppFocus", 3, "best fit focus: %f\n", minFocus);
+    psLogMsg ("ppFocus", 3, "fwhm @ min: %f\n", psPolynomial1DEval (poly, minFocus));
+    
+    psFree (poly);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppFocusGetFWHM.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppFocusGetFWHM.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppFocusGetFWHM.c	(revision 23312)
@@ -0,0 +1,79 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+bool ppFocusGetFWHM (pmConfig *config, psVector *focus, psVector *fwhm) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+    psMetadata *header;
+    float FOCUS, FWHM, FWHM_X, FWHM_Y, FWHMsum;
+    int FWHMnum;
+
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSPHOT.OUTPUT");
+    if (!status) {
+	psErrorStackPrint(stderr, "Can't find input data!\n");
+	exit(EXIT_FAILURE);
+    }
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // - find readouts with measured PSFs
+    // - measure the average central FWHM for each PSF
+    FWHMsum = 0.0;
+    FWHMnum = 0;
+
+    while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+        psLogMsg ("ppImageLoop", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+	while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psLogMsg ("ppImageLoop", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+	    // process each of the readouts
+	    while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+		if (!readout->data_exists) { continue; }
+
+		// get average FWHM
+		// psphotReadout writes the FWHM values into the PSPHOT.HEADER table
+		// the source of this value depends on the psphot options.
+		// - if breakPoint is set to PEAKS, the value will not be defined
+		// - if breakPoint is set to MOMENTS, the PSFSTAR moments are used
+		// - in all other cases, the psf model is used
+		header = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.HEADER");
+		if (header == NULL) {
+		    psError(PS_ERR_IO, false, "Missing header in ppFocus");
+		    continue;
+		}
+
+		FWHM_X = psMetadataLookupF32 (&status, header, "FWHM_X");
+		FWHM_Y = psMetadataLookupF32 (&status, header, "FWHM_Y");
+
+		FWHMsum += 0.5*(FWHM_X + FWHM_Y);
+		FWHMnum ++;
+
+		psLogMsg ("ppFocus", 4, "focus pt: %f,%f, fwhm sum: %f, fwhm num: %d\n", FWHM_X, FWHM_Y, FWHMsum, FWHMnum);
+	    }
+	}
+    }
+
+    FWHM = FWHMsum / FWHMnum;
+
+    FOCUS = psMetadataLookupF32 (&status, input->fpa->concepts, "FPA.FOCUS");
+
+    fwhm->data.F32[fwhm->n] = FWHM;
+    focus->data.F32[focus->n] = FOCUS;
+
+    psVectorExtend (fwhm, 10, 1);
+    psVectorExtend (focus, 10, 1);
+
+    psLogMsg ("ppFocus", 4, "focus: %f, fwhm: %f (%d)\n", FOCUS, FWHM, FWHMnum);
+
+    psFree (view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppFocusParseCamera.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppFocusParseCamera.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppFocusParseCamera.c	(revision 23312)
@@ -0,0 +1,133 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+// XXX clean up error checks: return NULL, not psAbort
+ppImageOptions *ppFocusParseCamera (pmConfig *config, int entry) {
+
+    bool status = false;
+
+    // the first input image defines the camera, and all recipes and options that follow
+    // select only the first file from the INPUT array
+    pmFPAfile *input = pmFPAfileDefineSingleFromArgs (&status, config, "PPIMAGE.INPUT", "INPUT", entry);
+    if (!status) {
+        psError(PS_ERR_IO, false, "Failed to build FPA from PPIMAGE.INPUT");
+        return NULL;
+    }
+
+    // parse the options from the metadata format to the ppImageOptions structure
+    ppImageOptions *options = ppImageOptionsParse (config);
+
+    // the following are defined from the argument list, if given,
+    // otherwise they revert to the config information or detrend database if specified
+    // not all input or output images are used in a given recipe
+    if (options->doBias) {
+	if (!ppImageDefineFile (config, input->fpa, "PPIMAGE.BIAS", "BIAS", PM_FPA_FILE_IMAGE, PM_DETREND_TYPE_BIAS)) {
+	    psError (PS_ERR_IO, false, "Can't find a bias image source");
+	    return NULL;
+	}
+    }
+    if (options->doDark) {
+	if (!ppImageDefineFile (config, input->fpa, "PPIMAGE.DARK", "DARK", PM_FPA_FILE_IMAGE, PM_DETREND_TYPE_DARK)) {
+	    psError (PS_ERR_IO, false, "Can't find a dark image source");
+	    return NULL;
+	}
+    }
+    if (options->doMask) {
+	if (!ppImageDefineFile (config, input->fpa, "PPIMAGE.MASK", "MASK", PM_FPA_FILE_MASK, PM_DETREND_TYPE_MASK)) {
+	    psError (PS_ERR_IO, false, "Can't find a mask image source");
+	    return NULL;
+	}
+    }
+    if (options->doFlat) {
+	if (!ppImageDefineFile (config, input->fpa, "PPIMAGE.FLAT", "FLAT", PM_FPA_FILE_IMAGE, PM_DETREND_TYPE_FLAT)) {
+	    psError (PS_ERR_IO, false, "Can't find a shutter image source");
+	    return NULL;
+	}
+    }
+
+    // the following files are output targets
+    pmFPAfile *output = pmFPAfileDefineOutput (config, input->fpa, "PPIMAGE.OUTPUT");
+    pmFPAfile *byChip = pmFPAfileDefineNewCamera (config, "PPIMAGE.OUTPUT.CHIP");
+    pmFPAfile *byFPA1 = pmFPAfileDefineNewCamera (config, "PPIMAGE.OUTPUT.FPA1");
+    pmFPAfile *byFPA2 = pmFPAfileDefineNewCamera (config, "PPIMAGE.OUTPUT.FPA2");
+
+    // save any of these files?
+    output->save = options->BaseFITS;
+    byChip->save = options->ChipFITS;
+    byFPA1->save = options->FPA1FITS;
+    byFPA2->save = options->FPA2FITS;
+
+    // output is used as a carrier: input to byChip
+    output->freeLevel = PM_FPA_LEVEL_CHIP;
+
+    // define the binned target files (which may just be carriers for some camera configurations)
+    pmFPAfile *bin1 = pmFPAfileDefineFromFPA (config, byChip->fpa, options->xBin1, options->yBin1, "PPIMAGE.BIN1");
+    pmFPAfile *bin2 = pmFPAfileDefineFromFPA (config, byChip->fpa, options->xBin2, options->yBin2, "PPIMAGE.BIN2");
+
+    // bin1 and bin2 are used as carriers: input for byFPA1, byFPA2
+    bin1->freeLevel = PM_FPA_LEVEL_FPA;
+    bin2->freeLevel = PM_FPA_LEVEL_FPA;
+
+    pmFPAfile *jpg1 = pmFPAfileDefineOutput (config, byFPA1->fpa, "PPIMAGE.JPEG1");
+    pmFPAfile *jpg2 = pmFPAfileDefineOutput (config, byFPA2->fpa, "PPIMAGE.JPEG2");
+
+    // XXX we could potentially not define these pmFPAfiles if no output is requested...
+    bin1->save = options->Bin1FITS;
+    bin2->save = options->Bin2FITS;
+    jpg1->save = options->Bin1JPEG;
+    jpg2->save = options->Bin2JPEG;
+
+    // Chip selection: turn on only the chips specified (pass status to suppress missing-key log msg)
+    char *chipLine = psMetadataLookupStr(&status, config->arguments, "CHIP_SELECTIONS");
+    psArray *chips = psStringSplitArray (chipLine, ",", false);
+    if (chips->n > 0) {
+        pmFPASelectChip (input->fpa, -1, true); // deselect all chips
+        for (int i = 0; i < chips->n; i++) {
+            int chipNum = atoi(chips->data[i]);
+            if (! pmFPASelectChip(input->fpa, chipNum, false)) {
+                psError(PS_ERR_IO, false, "Chip number %d doesn't exist in camera.\n", chipNum);
+                return false;
+            }
+        }
+    }
+    psFree (chips);
+
+    return (options);
+}
+
+// remove from config all pmFPAfiles which could have been added (ignore missing entries)
+void ppFocusDropCamera (pmConfig *config) {
+
+    psMetadataRemoveKey (config->files, "PPIMAGE.INPUT");
+
+    psMetadataRemoveKey (config->files, "PPIMAGE.BIAS");
+    psMetadataRemoveKey (config->files, "PPIMAGE.DARK");
+    psMetadataRemoveKey (config->files, "PPIMAGE.MASK");
+    psMetadataRemoveKey (config->files, "PPIMAGE.FLAT");
+
+    psMetadataRemoveKey (config->files, "PPIMAGE.OUTPUT");
+    psMetadataRemoveKey (config->files, "PPIMAGE.OUTPUT.CHIP");
+    psMetadataRemoveKey (config->files, "PPIMAGE.OUTPUT.FPA1");
+    psMetadataRemoveKey (config->files, "PPIMAGE.OUTPUT.FPA2");
+
+    psMetadataRemoveKey (config->files, "PPIMAGE.BIN1");
+    psMetadataRemoveKey (config->files, "PPIMAGE.BIN2");
+
+    psMetadataRemoveKey (config->files, "PPIMAGE.JPEG1");
+    psMetadataRemoveKey (config->files, "PPIMAGE.JPEG2");
+
+    psMetadataRemoveKey (config->files, "PSPHOT.INPUT");
+    psMetadataRemoveKey (config->files, "PSPHOT.OUTPUT");
+    psMetadataRemoveKey (config->files, "PSPHOT.RESID");
+    psMetadataRemoveKey (config->files, "PSPHOT.BACKMDL");
+    psMetadataRemoveKey (config->files, "PSPHOT.BACKGND");
+    psMetadataRemoveKey (config->files, "PSPHOT.BACKSUB");
+    psMetadataRemoveKey (config->files, "PSPHOT.PSF.LOAD");
+    psMetadataRemoveKey (config->files, "PSPHOT.PSF.SAVE");
+
+    return;
+}
+
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImage.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImage.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImage.c	(revision 23312)
@@ -0,0 +1,48 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+int main(int argc, char **argv) {
+
+    psLibInit(NULL);
+
+    psTimerStart(TIMER_TOTAL);
+
+    // Parse the configuration and arguments
+    // Open the input image(s)
+    // Determine camera, format from header if not already defined
+    // Construct camera in preparation for reading
+    pmConfig *config = ppImageArguments(argc, argv);
+    if (config == NULL) {
+        psErrorStackPrint(stderr, "Unable to parse command-line arguments.");
+        ppImageCleanup(config, NULL);
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    ppImageVersionPrint();
+
+    // define recipe options
+    // define the active I/O files
+    ppImageOptions *options = ppImageParseCamera(config);
+    if (options == NULL) {
+        psErrorStackPrint(stderr, "Unable to parse camera.");
+        ppImageCleanup(config, options);
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    // Image Arithmetic Loop
+    if (!ppImageLoop(config, options)) {
+        psErrorStackPrint(stderr, "Unable to loop over input");
+        ppImageCleanup(config, options);
+        exit(PS_EXIT_SYS_ERROR);
+    }
+
+    psLogMsg("ppImage", PS_LOG_INFO, "Complete ppImage run: %f sec\n", psTimerMark(TIMER_TOTAL));
+
+    // Cleaning up
+    ppImageCleanup(config, options);
+
+    return PS_EXIT_SUCCESS;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImage.h
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImage.h	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImage.h	(revision 23312)
@@ -0,0 +1,244 @@
+#ifndef PP_IMAGE_H
+#define PP_IMAGE_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <strings.h>  // for strcasecmp
+#include <unistd.h>   // for unlink
+#include "pslib.h"
+#include "psmodules.h"
+#include "psphot.h"
+#include "psastro.h"
+#include "ppStats.h"
+
+#define RECIPE_NAME "PPIMAGE"           // Name of the recipe to use
+#define TIMER_TOTAL   "PPIMAGE.TOTAL"   // Name of timer for total time
+#define TIMER_DETREND "PPIMAGE.DETREND" // Name of timer for detrend time
+#define TIMER_PHOT    "PPIMAGE.PHOT"    // Name of timer for photometry time
+
+// Options for ppImage processing
+typedef struct {
+    // actions which ppImage should perform
+    bool doMaskBuild;                   // Build internal mask
+    bool doVarianceBuild;               // Build internal variance map
+    bool doMask;                        // Mask bad pixels
+    bool doNonLin;                      // Non-linearity correction
+    bool doOverscan;                    // Overscan subtraction
+    bool doBias;                        // Bias subtraction
+    bool doDark;                        // Dark subtraction
+    bool doRemnance;                    // Remnance masking
+    bool doShutter;                     // Shutter correction
+    bool doFlat;                        // Flat-field normalisation
+    bool doFringe;                      // Fringe subtraction
+    bool doPhotom;                      // Source identification and photometry
+    bool doBG;                          // Background subtraction
+    bool doAstromChip;                  // per-chip Astrometry
+    bool doAstromMosaic;                // full-mosaic Astrometry
+    bool doStats;                       // call ppStats on the image
+
+    // output files requested
+    bool BaseFITS;
+    bool BaseMaskFITS;
+    bool BaseVarianceFITS;
+
+    bool ChipFITS;
+    bool ChipMaskFITS;
+    bool ChipVarianceFITS;
+
+    bool FPA1FITS;
+    bool FPA2FITS;
+    bool Bin1FITS;
+    bool Bin1JPEG;
+    bool Bin2FITS;
+    bool Bin2JPEG;
+
+    // make values for abstract concepts of masking
+    psImageMaskType maskValue;          // apply this bit-mask to choose masked bits
+    psImageMaskType markValue;          // apply this bit-mask to choose masked bits
+    psImageMaskType satMask;            // Mask value to give saturated pixels
+    psImageMaskType badMask;            // Mask value to give bad pixels
+    psImageMaskType flatMask;           // Mask value to give bad flat pixels
+    psImageMaskType blankMask;          // Mask value to give blank pixels
+
+    // non-linear correction parameters
+    psDataType nonLinearType;
+    psMetadataItem *nonLinearData;
+    void *nonLinearSource;
+
+    // options for the analysis
+    pmOverscanOptions *overscan;        // Overscan options
+
+    // binning parameters
+    int xBin1;                          // x-binning, scale 1
+    int yBin1;                          // y-binning, scale 1
+    int xBin2;                          // x-binning, scale 2
+    int yBin2;                          // y-binning, scale 2
+
+    // parameters used by the fringe analysis
+    float fringeRej;                    // Fringe rejection limit
+    int fringeIter;                     // Fringe iterations
+    float fringeKeep;                   // Fringe keep fraction
+
+    int remnanceSize;                   // Size for remnance detection
+    float remnanceThresh;               // Threshold for remnance detection
+
+    char *normClass;			// class to use for per-class normalization 
+} ppImageOptions;
+
+// Cells to be used in the detrend
+typedef struct {
+    pmCell *input;                      // The input cell, to be operated upon
+    pmCell *mask;                       // The bad pixel mask
+    pmCell *bias;                       // The bias correction
+    pmCell *dark;                       // The dark correction
+    pmCell *flat;                       // The flat-field correction
+} ppImageDetrend;
+
+ppImageOptions *ppImageOptionsAlloc(void);
+
+// Determine the processing options
+ppImageOptions *ppImageOptionsParse(pmConfig *config);
+
+// Get the configuration
+pmConfig *ppImageArguments(int argc, char **argv);
+
+// Determine what type of camera, and initialise
+ppImageOptions *ppImageParseCamera(pmConfig *config);
+bool ppImageSetMaskBits (pmConfig *config, ppImageOptions *options);
+
+// Loop over the input
+bool ppImageLoop(pmConfig *config, ppImageOptions *options);
+
+// free memory, check for leaks
+void ppImageCleanup (pmConfig *config, ppImageOptions *options);
+
+// perform the detrend analysis on the current readout
+bool ppImageDetrendReadout (pmConfig *config, ppImageOptions *options, pmFPAview *view);
+
+bool ppImageDetrendBias(pmReadout *inputReadout, pmReadout *bias, pmReadout *dark, ppImageOptions *options);
+
+bool ppImageDetrendNonLinear(pmReadout *input, ppImageOptions *options);
+bool ppImageDetrendNonLinearLookup(pmReadout *input, psMetadataItem *dataItem);
+bool ppImageDetrendNonLinearPolynomial(pmReadout *input, psMetadataItem *dataItem);
+
+bool ppImageDetrendCell(ppImageDetrend *detrend, ppImageOptions *options, pmConfig *config);
+bool ppImageDetrendBias(pmReadout *inputReadout, pmReadout *bias, pmReadout *dark, ppImageOptions *options);
+pmReadout* ppImageDetrendSelectFirst(pmCell *cell, char *name, bool doThis);
+
+bool ppImageDetrendFree(pmConfig *config, pmFPAview *view);
+bool ppImageFringeFree(pmConfig *config, pmFPAview *view);
+
+// Record which detrend file was used for the detrending
+bool ppImageDetrendRecord(
+    pmCell *cell,                       // Cell of interest
+    const pmConfig *config,             // Configuration
+    const ppImageOptions *options,      // Processing options
+    const pmFPAview *view               // View to cell
+    );
+
+bool ppImageRebinChip (pmConfig *config, pmFPAview *view, ppImageOptions *options, char *outName);
+
+bool ppImagePhotom (pmConfig *config, pmFPAview *view);
+bool ppImageAstrom (pmConfig *config);
+bool ppImageAddstar (pmConfig *config);
+
+// Subtract background from the chip-mosaicked image
+bool ppImageSubtractBackground(
+    pmConfig *config,                   // Configuration
+    const pmFPAview *view,              // View to chip of interest
+    const ppImageOptions *options       // Processing options
+    );
+
+bool ppImageMosaicChip (pmConfig *config, const ppImageOptions *options, const pmFPAview *view,
+                        const char *outFile, const char *inFile);
+bool ppImageMosaicFPA (pmConfig *config, const ppImageOptions *options,
+                       const char *outFile, const char *inFile);
+
+bool ppImageSetMaskBits (pmConfig *config, ppImageOptions *options);
+
+void ppImageFileCheck (pmConfig *config);
+
+// functions used by ppFocus
+pmConfig *ppFocusArguments(int argc, char **argv);
+ppImageOptions *ppFocusParseCamera (pmConfig *config, int entry);
+bool ppFocusGetFWHM (pmConfig *config, psVector *focus, psVector *fwhm);
+bool ppFocusFitFWHM (pmConfig *config, psVector *focus, psVector *fwhm);
+
+void ppFocusDropCamera (pmConfig *config);
+
+bool ppImageDefineFile (pmConfig *config, pmFPA *input, char *filerule, char *argname, pmFPAfileType fileType, pmDetrendType detrendType);
+
+// write stats to output file
+bool ppImageStatsOutput(pmConfig *config, // Configuration
+                        psMetadata *stats, // Statistics output
+                        const ppImageOptions *options // Options
+    );
+
+
+// Measure fringes
+bool ppImageDetrendFringeMeasure(pmReadout *readout, // Readout to measure
+                                 pmCell *fringe, // Fringe cell (each readout is a different component)
+                                 const bool isResidual,
+                                 const ppImageOptions *options // Options
+    );
+
+// Solve the fringe system
+bool ppImageDetrendFringeSolve(pmChip *scienceChip, // Chip with science
+                               const pmChip *refChip, // Chip with reference fringes
+                               const bool isResidual,
+                               const ppImageOptions *options // Options
+    );
+
+// Generate fringe frame
+bool ppImageDetrendFringeGenerate(pmCell *science, // Science cell
+                                  pmCell *fringes, // Fringe cell, one readout per fringe component
+                                  const ppImageOptions *options // Options
+    );
+
+
+bool ppImageDetrendFringeApply (pmConfig *config, // config
+                                pmChip *chip, // science chip
+                                const pmFPAview *inputView, // current view
+                                const ppImageOptions *options // options
+    );
+
+/// Return short version information
+psString ppImageVersion(void);
+
+/// Return software source
+psString ppImageSource(void);
+
+/// Return long version information
+psString ppImageVersionLong(void);
+
+/// Populate the header with version information for all dependencies
+bool ppImageVersionHeader(psMetadata *metadata ///< Header to populate
+    );
+
+/// Print version information
+void ppImageVersionPrint(void);
+
+
+// calculate stats, including MD5
+bool ppImagePixelStats(pmConfig *config,// Configuration
+                       psMetadata *stats, // Statistics output
+                       const ppImageOptions *options, // Options
+                       const pmFPAview *inputView // View to data
+    );
+
+// calculate stats from headers and concepts
+bool ppImageMetadataStats(pmConfig *config, // Configuration
+                          psMetadata *stats, // Statistics output
+                          const ppImageOptions *options // Options
+    );
+
+void ppImageFileCheck(pmConfig *config);
+
+/// Dump memory summary to text file
+void ppImageMemoryDump(const char *description);
+
+#endif
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageAddstar.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageAddstar.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageAddstar.c	(revision 23312)
@@ -0,0 +1,120 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+bool setFilename (pmFPAfile *file, pmFPAview *view);
+bool addstarFile (char *addstarCommand, char *filename);
+
+bool ppImageAddstar (pmConfig *config) {
+
+    bool status;
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    // select recipe options supplied on command line
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, RECIPE_NAME);
+
+    // find a pmFPAfile PSASTRO.OUTPUT
+    pmFPAfile *file = psMetadataLookupPtr (&status, config->files, "PSASTRO.OUTPUT");
+    if (!status) {
+        psError (PS_ERR_IO, true, "no PSASTRO.OUTPUT file defined");
+        return false;
+    }
+
+    // find the addstar command: %s is replaced with name of output file
+    char *addstarCommand = psMetadataLookupStr (&status, recipe, "ADDSTAR.COMMAND");
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    if (file->fileLevel == PM_FPA_LEVEL_CHIP) {
+        // call addstar on this file
+        setFilename (file, view);
+        addstarFile (addstarCommand, file->filename);
+        return true;
+    }
+
+    while ((chip = pmFPAviewNextChip (view, file->fpa, 1)) != NULL) {
+        psLogMsg ("ppImageAddstar", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+        if (file->fileLevel == PM_FPA_LEVEL_CHIP) {
+            // call addstar on this file
+            setFilename (file, view);
+            addstarFile (addstarCommand, file->filename);
+            continue;
+        }
+
+        while ((cell = pmFPAviewNextCell (view, file->fpa, 1)) != NULL) {
+            psLogMsg ("ppImageAddstar", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (! cell->process || ! cell->file_exists) { continue; }
+
+            if (file->fileLevel == PM_FPA_LEVEL_CELL) {
+                // call addstar on this file
+                setFilename (file, view);
+                addstarFile (addstarCommand, file->filename);
+                continue;
+            }
+
+            // process each of the readouts
+            while ((readout = pmFPAviewNextReadout (view, file->fpa, 1)) != NULL) {
+                if (! readout->data_exists) { continue; }
+
+                if (file->fileLevel == PM_FPA_LEVEL_READOUT) {
+                    // call addstar on this file
+                    setFilename (file, view);
+                    addstarFile (addstarCommand, file->filename);
+                    continue;
+                } else {
+                    psError (PS_ERR_IO, true, "inconsistent fileLevel for %s: %d\n", file->name, file->fileLevel);
+                    return false;
+                }
+            }
+        }
+    }
+    return true;
+}
+
+bool setFilename (pmFPAfile *file, pmFPAview *view) {
+
+    // determine the file name
+    // free a name allocated earlier
+    psFree (file->filename);
+    file->filename = pmFPAfileNameFromRule (file->filerule, file, view);
+    if (file->filename == NULL) {
+        psError(PS_ERR_IO, true, "Filename is NULL");
+        return false;
+    }
+
+    // indirect filenames are not allowed for output
+    if (!strcasecmp (file->filename, "@FILES")) {
+        psError(PS_ERR_IO, true, "indirect filenames are not allowed for output : %s\n", file->name);
+        return false;
+    }
+    if (!strcasecmp (file->filename, "@DETDB")) {
+        psError(PS_ERR_IO, true, "detrend db filenames are not allowed for output : %s\n", file->name);
+        return false;
+    }
+    return true;
+}
+
+bool addstarFile (char *addstarCommand, char *filename) {
+
+    bool status;
+    char *addstarLine = psStringCopy(addstarCommand);
+    psStringSubstitute(&addstarCommand, filename, "%s");
+
+    // catch addstar stderr/stdout and do what?
+    psIOBuffer *buffer = psIOBufferAlloc (512);
+    psPipe *pipe = psPipeOpen (addstarLine);
+    status = psIOBufferReadEmpty (buffer, 100, pipe->fd_stdout);
+    if (!status) {
+        psError (PS_ERR_IO, false, "detselect is not responding");
+    }
+    psFree (addstarLine);
+    psFree (buffer);
+    psPipeClose (pipe);
+    psFree (pipe);
+    return status;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageArguments.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageArguments.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageArguments.c	(revision 23312)
@@ -0,0 +1,163 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+static void usage (void) {
+    fprintf(stderr, "USAGE: ppImage [-file INPUT.fits] [-list INPUT.txt] OUTPUT\n\n");
+    fprintf(stderr, "Optional arguments:\n");
+    fprintf(stderr, "\t-stats STATS.mdc: Output statistics into STATS.mdc\n");
+    fprintf(stderr, "\t-isfringe: The input image contains fringe data.\n");
+    fprintf(stderr, "\t-isdark: The input image contains dark data.\n");
+    fprintf(stderr, "\t-usemask MASKVAL: Use this mask value (override recipe).\n");
+    fprintf(stderr, "\t-chip CHIPNUM: Only process this chip number.\n");
+    fprintf(stderr, "\t-norm VALUE: Divide through by this value when done.\n");
+    fprintf(stderr, "\t-normlist file.mdc: normalizations by class_id.\n");
+    fprintf(stderr, "\n");
+    fprintf(stderr, "Input options (single file / file list):\n");
+    fprintf(stderr, "\t-bias/-biaslist: Bias image.\n");
+    fprintf(stderr, "\t-dark/-darklist: Dark image.\n");
+    fprintf(stderr, "\t-shutter/-shutterlist: Shutter image.\n");
+    fprintf(stderr, "\t-flat/-flatlist: Flat image.\n");
+    fprintf(stderr, "\t-mask/-masklist: Mask image.\n");
+    fprintf(stderr, "\t-fringe/-fringelist: Fringe image and data.\n");
+    fprintf(stderr, "\n");
+    exit (2);
+}
+
+pmConfig *ppImageArguments(int argc, char **argv)
+{
+    int argnum;                         // Argument number of interest
+
+    if (argc == 1) {
+        usage();
+    }
+
+    if (psArgumentGet (argc, argv, "-version")) {
+        psString version;
+        version = ppImageVersionLong();   fprintf (stdout, "%s\n", version); psFree (version);
+        version = ppStatsVersionLong();   fprintf (stdout, "%s\n", version); psFree (version);
+        version = psphotVersionLong();    fprintf (stdout, "%s\n", version); psFree (version);
+        version = psastroVersionLong();   fprintf (stdout, "%s\n", version); psFree (version);
+        version = psModulesVersionLong(); fprintf (stdout, "%s\n", version); psFree (version);
+        version = psLibVersionLong();     fprintf (stdout, "%s\n", version); psFree (version);
+        exit (0);
+    }
+
+    // load the site-wide configuration information
+    pmConfig *config = pmConfigRead(&argc, argv, RECIPE_NAME);
+    if (config == NULL) {
+        psErrorStackPrint(stderr, "Can't find site configuration!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PPIMAGE recipe values loaded from recipe files
+    psMetadata *options = pmConfigRecipeOptions (config, RECIPE_NAME);
+
+    // save these recipe options until we have loaded the options
+    // psMetadata *options = psMetadataAlloc ();
+    // psMetadataAddPtr (config->arguments, PS_LIST_TAIL, "PPIMAGE.OPTIONS",  PS_DATA_METADATA, "", options);
+
+    if ((argnum = psArgumentGet(argc, argv, "-stats"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "STATS", PS_META_REPLACE,
+                         "Filename for summary statistics", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-isfringe"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddBool(config->arguments, PS_LIST_TAIL, "INPUT_IS_FRINGE", PS_META_REPLACE,
+                          "Input is fringe image", true);
+    }
+    if ((argnum = psArgumentGet(argc, argv, "-isdark"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddBool(config->arguments, PS_LIST_TAIL, "INPUT_IS_DARK", PS_META_REPLACE,
+                          "Input is dark image", true);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-visual"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        pmVisualSetVisual(true);
+    }
+
+    // Number of threads
+    if ((argnum = psArgumentGet(argc, argv, "-threads"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        int nThreads = atoi(argv[argnum]);
+        psMetadataAddS32(config->arguments, PS_LIST_TAIL, "NTHREADS", 0, "number of warp threads", nThreads);
+        psArgumentRemove(argnum, &argc, argv);
+
+        // create the thread pool with number of desired threads, supplying our thread launcher function
+        // XXX need to determine the number of threads from the config data
+        psThreadPoolInit (nThreads);
+    }
+
+    // the following options override the PPIMAGE recipe options
+
+    // recipe option: -usemask : override MASK setting in recipe
+    if ((argnum = psArgumentGet(argc, argv, "-usemask"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddBool(options, PS_LIST_TAIL, "MASK", PS_META_REPLACE, "", true);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    // XXX add other PPIMAGE recipe options here
+
+    // the input file is a required argument; if not found, we will exit
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT", "-file", "-list");
+
+    // if these command-line options are supplied, load the file name lists into config->arguments
+    // override any configuration-specified source for these files
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "BIAS", "-bias", "-biaslist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "DARK", "-dark", "-darklist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "SHUTTER", "-shutter", "-shutterlist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "FLAT", "-flat", "-flatlist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "MASK", "-mask", "-masklist");
+    pmConfigFileSetsMD (config->arguments, &argc, argv, "FRINGE", "-fringe", "-fringelist");
+
+    // chip selection is used to limit chips to be processed
+    if ((argnum = psArgumentGet (argc, argv, "-chip"))) {
+        psArgumentRemove (argnum, &argc, argv);
+        psMetadataAddStr (config->arguments, PS_LIST_TAIL, "CHIP_SELECTIONS", PS_DATA_STRING, "", argv[argnum]);
+        psArgumentRemove (argnum, &argc, argv);
+    }
+
+    // Optional normalization factor
+    if ((argnum = psArgumentGet(argc, argv, "-norm"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        float norm = atof(argv[argnum]);
+        psMetadataAddF32(config->arguments, PS_LIST_TAIL, "NORMALIZATION", 0,
+                         "Normalisation to apply", norm);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    // Optional per-class normalization table
+    if ((argnum = psArgumentGet(argc, argv, "-normlist"))) {
+        psArgumentRemove(argnum, &argc, argv);
+
+        unsigned int nFail = 0;
+        psMetadata *normlist = psMetadataConfigRead (NULL, &nFail, argv[argnum], false);
+        // XXX allow this file to be in nebulous?
+
+        psMetadataAddMetadata(config->arguments, PS_LIST_TAIL, "NORMALIZATION.TABLE", 0, "Normalization to apply", normlist);
+        psFree (normlist);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-dumpconfig"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "DUMP_CONFIG", PS_META_REPLACE,
+                         "Filename for configuration dump", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if (argc != 2) usage ();
+
+    // Add the input and output images (which remain on the command-line) to the arguments list
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "Name of the output image", argv[1]);
+
+    return config;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageAstrom.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageAstrom.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageAstrom.c	(revision 23312)
@@ -0,0 +1,66 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+// this function is mostly equivalent to the top-level of psastro, with some
+// modifications since the data has already been loaded.
+bool ppImageAstrom (pmConfig *config) {
+
+    bool status;
+
+    // select recipe options supplied on command line
+    // XXX move these options to the "PSASTRO" recipe?
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSASTRO_RECIPE);
+
+    // find or define a pmFPAfile PSPHOT.INPUT
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSASTRO.INPUT");
+    PS_ASSERT (input, false);
+
+    ppImageMemoryDump("astrom");
+
+    // convert the output sources created by psphot into astrometry objects
+    if (!psastroConvertFPA (input->fpa, recipe)) {
+        psError (PSASTRO_ERR_UNKNOWN, false, "error reading input data\n");
+        return false;
+    }
+
+    if (!psastroAnalysis (config)) {
+        psError (PSASTRO_ERR_UNKNOWN, false, "failure in psastro analysis\n");
+        return false;
+    }
+
+    // deactivate the psastro files, reactive when needed
+    pmFPAfileActivate (config->files, false, NULL);
+    pmFPAfileActivate (config->files, true, "PSASTRO.OUTPUT");
+
+    // loop over all chips and perform IO needed
+    pmChip *chip = NULL;
+    pmFPAview *view = pmFPAviewAlloc(0);// View for level of interest
+    while ((chip = pmFPAviewNextChip(view, input->fpa, 1)) != NULL) {
+        psLogMsg ("ppImageLoop", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+
+        ppImageMemoryDump("astrom");
+
+        // Output and Close at Chip level
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+            psFree(view);
+            return false;
+        }
+    }
+
+    // Output and Close FPA
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+        psFree(view);
+        return false;
+    }
+
+    // deactivate the PSASTRO files, re-active all else
+    // XXX do we need a way to activate / deactivate other groups?
+    pmFPAfileActivate (config->files, true, NULL);
+    pmFPAfileActivate (config->files, false, "PSASTRO.OUTPUT");
+
+    psFree(view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageCleanup.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageCleanup.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageCleanup.c	(revision 23312)
@@ -0,0 +1,30 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+void ppImageCleanup (pmConfig *config, ppImageOptions *options)
+{
+    // Free memory used by ppImage
+    psFree(options);
+    psFree(config);
+
+    // Free memory used by psModules
+    pmSourceFitSetDone ();
+    pmConceptsDone();
+    pmConfigDone();
+    pmModelClassCleanup();
+
+    // Free memory used by psLib
+    psLibFinalize();
+
+    // psMemBlock **memblocks;
+    // int Nleaks = psMemCheckLeaks (0, &memblocks, stderr, false);
+    // fprintf (stderr, "Found %d leaks at %s\n", Nleaks, "ppImage");
+
+    // fprintf(stderr, "Found %d leaks at %s\n", psMemCheckLeaks (0, NULL, NULL, false), "ppImage");
+    fprintf(stderr, "Found %d leaks at %s\n", psMemCheckLeaks (0, NULL, stdout, false), "ppImage");
+
+    return;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDefineFile.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDefineFile.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDefineFile.c	(revision 23312)
@@ -0,0 +1,56 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+bool ppImageDefineFile(pmConfig *config, pmFPA *input, char *filerule, char *argname,
+                       pmFPAfileType fileType, pmDetrendType detrendType)
+{
+    bool status;
+    pmFPAfile *file = NULL;             // File to be defined
+
+    if (!file) {
+        // look for the file on the RUN metadata
+        file = pmFPAfileDefineFromRun(&status, config, filerule);
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to load file definition");
+            return false;
+        }
+    }
+    if (!file) {
+        // look for the file on the argument list
+        file = pmFPAfileDefineFromArgs(&status, config, filerule, argname);
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to load file definition");
+            return false;
+        }
+    }
+    if (!file) {
+        // look for the file in the camera config table
+        file = pmFPAfileDefineFromConf(&status, config, filerule);
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to load file definition");
+            return false;
+        }
+    }
+    if (!file) {
+        // look for the file to be loaded from the detrend database
+        file = pmFPAfileDefineFromDetDB(&status, config, filerule, input, detrendType);
+        if (!status) {
+            psError(PS_ERR_UNKNOWN, false, "failed to load file definition");
+            return false;
+        }
+    }
+
+    if (!file) {
+        return false;
+    }
+
+    if (file->type != fileType) {
+        psError(PS_ERR_IO, true, "%s is not of type %s", filerule, pmFPAfileStringFromType(fileType));
+        return false;
+    }
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDetrendFree.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDetrendFree.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDetrendFree.c	(revision 23312)
@@ -0,0 +1,69 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+// list of the detrend types to free
+static char *detrendTypes[] = {
+    "PPIMAGE.MASK",
+    "PPIMAGE.BIAS",
+    "PPIMAGE.DARK",
+    "PPIMAGE.FLAT",
+    "PPIMAGE.SHUTTER",
+    NULL
+};
+
+bool ppImageDetrendFree (pmConfig *config, pmFPAview *view) {
+
+    bool status;
+
+    for (int i = 0; detrendTypes[i] != NULL; i++) {
+
+	pmFPAfile *file = psMetadataLookupPtr(&status, config->files, detrendTypes[i]); // File of interest
+	if (!file) continue; // not all detrends are used in any given run
+
+	// this only returns false on a failure.  if we are not ready to write or close, it is not an error
+	if (!pmFPAfileWrite (file, view, config)) {
+	    psError(PS_ERR_IO, false, "failed to WRITE %s", file->name);
+	    return false;
+	}
+	if (!pmFPAfileClose(file, view)) {
+	    psError(PS_ERR_IO, false, "failed to CLOSE for %s", file->name);
+	    return false;
+	}
+	if (!pmFPAfileFreeData(file, view)) {
+	    if (!psMetadataRemoveKey(config->files, file->name)) {
+		psError(PS_ERR_IO, false, "failed to remove %s in FPA_AFTER block", file->name);
+		return false;
+	    }
+	}
+    }
+    return true;
+}
+
+bool ppImageFringeFree (pmConfig *config, pmFPAview *view) {
+
+    bool status;
+
+    pmFPAfile *file = psMetadataLookupPtr(&status, config->files, "PPIMAGE.FRINGE"); // File of interest
+    if (!file) return true;
+
+    // this only returns false on a failure.  if we are not ready to write or close, it is not an error
+    if (!pmFPAfileWrite (file, view, config)) {
+	psError(PS_ERR_IO, false, "failed to WRITE %s", file->name);
+	return false;
+    }
+    if (!pmFPAfileClose(file, view)) {
+	psError(PS_ERR_IO, false, "failed to CLOSE for %s", file->name);
+	return false;
+    }
+    if (!pmFPAfileFreeData(file, view)) {
+	if (!psMetadataRemoveKey(config->files, file->name)) {
+	    psError(PS_ERR_IO, false, "failed to remove %s in FPA_AFTER block", file->name);
+	    return false;
+	}
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDetrendFringe.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDetrendFringe.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDetrendFringe.c	(revision 23312)
@@ -0,0 +1,351 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+bool ppImageDetrendFringeMeasure(pmReadout *readout, pmCell *fringe, const bool isResidual, const ppImageOptions *options)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(fringe, false);
+    PS_ASSERT_PTR_NON_NULL(options, false);
+
+    // Reference fringe measurements
+    psArray *references = psMemIncrRefCounter(psMetadataLookupPtr(NULL, fringe->analysis,
+                                                                  "FRINGE.MEASUREMENTS"));
+    if (!references) {
+        references = pmFringesParse(fringe); // Reference fringes
+        if (!references) {
+            psError(PS_ERR_IO, false, "Unable to find fringe references.\n");
+            return false;
+        }
+        psMetadataAdd(fringe->analysis, PS_LIST_TAIL, "FRINGE.MEASUREMENTS", PS_DATA_UNKNOWN,
+                      "Fringe measurements", references);
+    }
+
+    pmFringeStats *reference = references->data[0]; // Take the first as representative
+    pmFringeRegions *regions = reference->regions; // Regions to measure
+    pmFringeStats *measurements = pmFringeStatsMeasure(regions, readout, options->maskValue); // Fringe stats
+
+    // Normalise measurements by the exposure time
+    bool mdok;                          // Status of MD lookup
+    float expTime = psMetadataLookupF32(&mdok, readout->parent->concepts, "CELL.EXPOSURE"); // Exp. time
+    if (!mdok || !isfinite(expTime)) {
+        psError(PS_ERR_UNKNOWN, false, "CELL.EXPOSURE is not set for --- can't normalise fringes\n");
+        psFree(measurements);
+        return false;
+    }
+    if (expTime == 0) {
+        psWarning("Exposure time is zero --- are you sure you want fringe subtraction?\n");
+        expTime = 1.0;
+    }
+    // normalize by exposure time : NOTE df is 1/sigma
+    psBinaryOp(measurements->f, measurements->f, "*", psScalarAlloc(1.0 / expTime, PS_TYPE_F32));
+    psBinaryOp(measurements->df, measurements->df, "*", psScalarAlloc(expTime, PS_TYPE_F32));
+
+    char *scienceFringes = NULL;
+    if (isResidual) {
+	scienceFringes = psStringCopy ("FRINGE.RESIDUALS");
+    } else {
+	scienceFringes = psStringCopy ("FRINGE.MEASUREMENTS");
+    }
+
+    // Science fringe measurements
+    pmFringeStats *previous = psMetadataLookupPtr(NULL, readout->parent->analysis, scienceFringes);
+    if (previous) {
+        // Multiple readouts: concatenate
+        psArray *concatenate = psArrayAlloc(2); // Array to hold fringes
+
+        // Concatenate science measurements
+        concatenate->data[0] = previous;
+        concatenate->data[1] = measurements;
+        pmFringeStats *new = pmFringeStatsConcatenate(concatenate, NULL, NULL); // New measurements
+        psFree(measurements);
+        measurements = new;
+
+        // Concatenate reference measurements (duplication, so the science and reference line up)
+        for (int i = 0; i < references->n; i++) {
+            concatenate->data[0] = concatenate->data[1] = references->data[i];
+            pmFringeStats *refNew = pmFringeStatsConcatenate(concatenate, NULL, NULL);
+            psFree(references->data[i]);
+            references->data[i] = refNew;
+        }
+        concatenate->data[0] = concatenate->data[1] = NULL;
+        psFree(concatenate);
+    }
+
+    psMetadataAdd(readout->parent->analysis, PS_LIST_TAIL, scienceFringes,
+                  PS_DATA_UNKNOWN | PS_META_REPLACE, "Fringe measurements", measurements);
+    psFree(measurements);
+    psFree(scienceFringes);
+    psFree(references);
+
+    return true;
+}
+
+
+// Pull the fringes out of the cell analysis FRINGE.MEASUREMENTS for a chip
+// XXX need some error checks
+static psArray *getFringes(const pmChip *chip, const char *source)
+{
+    psArray *cells = chip->cells;       // Component cells
+    psArray *fringes = psArrayAlloc(cells->n); // Fringes, to return
+    for (int i = 0; i < cells->n; i++) {
+        pmCell *cell = cells->data[i];  // Cell of interest
+
+	// XXX for now, skip the video cells (cell->readouts->n > 1)
+	if (cell->readouts->n > 1) {
+	  psWarning ("Skipping Video Cell for ppImageDetrendFringe.c:getFringes");
+	  continue;
+	}
+
+        fringes->data[i] = psMemIncrRefCounter(psMetadataLookupPtr(NULL, cell->analysis, source));
+    }
+
+    return fringes;
+}
+
+
+// Solve the fringe system: we have science fringe measurements for each cell, and an array of reference
+// fringe measurements for each cell.  Need to concatenate these together first, and then solve.
+bool ppImageDetrendFringeSolve(pmChip *scienceChip, const pmChip *refChip, const bool isResidual, const ppImageOptions *options)
+{
+    PS_ASSERT_PTR_NON_NULL(scienceChip, NULL);
+    PS_ASSERT_PTR_NON_NULL(refChip, NULL);
+    PS_ASSERT_PTR_NON_NULL(options, NULL);
+
+    psArray *science = NULL;
+    if (isResidual) {
+	science = getFringes(scienceChip, "FRINGE.RESIDUALS"); // Fringe residuals on science chip
+    } else {
+	science = getFringes(scienceChip, "FRINGE.MEASUREMENTS"); // Fringe measurements on science chip
+    }
+
+    pmFringeStats *scienceCat = pmFringeStatsConcatenate(science, NULL, NULL); // Science fringes
+    psFree(science);
+
+    // Need to transform the array of cells each with an array of fringes --> array of fringes for the chip as
+    // a whole
+    psArray *references = getFringes(refChip, "FRINGE.MEASUREMENTS"); // Fringe measurements on reference chip
+    int numRefs = ((psArray*)references->data[0])->n; // Number of reference fringes
+    psArray *referencesCat = psArrayAlloc(numRefs); // Reference fringes
+    for (int i = 0; i < numRefs; i++) { // Iterate over fringes
+        psArray *refs = psArrayAlloc(references->n); // Array of fringes for each cell
+        for (int j = 0; j < references->n; j++) { // Iterate over cells
+            psArray *ref = references->data[j]; // Array of references for this cell
+            refs->data[j] = psMemIncrRefCounter(ref->data[i]);
+        }
+        referencesCat->data[i] = pmFringeStatsConcatenate(refs, NULL, NULL);
+        psFree(refs);
+    }
+    psFree(references);
+
+    // Now we can solve
+    psTrace("ppImage", 3, "Solving fringe system...\n");
+    pmFringeScale *solution = pmFringeScaleMeasure(scienceCat, referencesCat, options->fringeRej,
+                                                   options->fringeIter, options->fringeKeep);
+
+    if (isResidual) {
+	psMetadataAdd(scienceChip->analysis, PS_LIST_TAIL, "FRINGE.RESIDUAL.SOLUTION", PS_DATA_UNKNOWN, "Fringe solution", solution);
+    } else {
+	psMetadataAdd(scienceChip->analysis, PS_LIST_TAIL, "FRINGE.SOLUTION", PS_DATA_UNKNOWN, "Fringe solution", solution);
+    }
+
+# if (0)
+    // write the fringe amplitude or residual amplitude to the header
+    // XXX this is measured per cell, but we only have headers per chip
+    pmHDU *hdu = pmHDUFromCell(science);// HDU  of interest
+    for (int i = 0; i < solution->nFringeFrames; i++) {
+	// write metadata header value
+	psString keyword = NULL;
+	if (isResidual) {
+	    psStringAppend (&keyword, "FRES_%02dV", i);
+	} else {
+	    psStringAppend (&keyword, "FRNG_%02dV", i);
+	}
+	psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "Fringe Amplitude", solution->coeff->data.F32[i + 1]);
+	psFree (keyword);
+
+	keyword = NULL;
+	if (isResidual) {
+	    psStringAppend (&keyword, "FRES_%02dE", i);
+	} else {
+	    psStringAppend (&keyword, "FRNG_%02dE", i);
+	}
+	psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "Fringe Amplitude error", solution->coeffErr->data.F32[i + 1]);
+	psFree (keyword);
+    }
+# endif
+
+    psFree(solution);
+    psFree(scienceCat);
+    psFree(referencesCat);
+
+    return true;
+}
+
+
+bool ppImageDetrendFringeGenerate(pmCell *science, pmCell *fringes, const ppImageOptions *options)
+{
+    PS_ASSERT_PTR_NON_NULL(science, false);
+    PS_ASSERT_PTR_NON_NULL(fringes, false);
+
+    pmFringeScale *solution = psMetadataLookupPtr(NULL, science->parent->analysis, "FRINGE.SOLUTION");
+    if (!solution) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find fringe solution.\n");
+        return false;
+    }
+    assert(fringes->readouts->n == solution->nFringeFrames);
+
+    bool mdok;                          // Status of MD lookup
+    float expTime = psMetadataLookupF32(&mdok, science->concepts, "CELL.EXPOSURE"); // Exp. time
+    if (!mdok || !isfinite(expTime)) {
+        psError(PS_ERR_UNKNOWN, true, "CELL.EXPOSURE is not set --- can't renormalise fringes\n");
+        return false;
+    }
+    if (expTime == 0) {
+        psWarning("Exposure time is zero --- are you sure you want fringe subtraction?\n");
+        expTime = 1.0;
+    }
+
+    pmHDU *hdu = pmHDUFromCell(science);// HDU  of interest
+
+    // Construct the fringe image from the scale
+    psTrace("ppImage", 3, "Generating fringe correction...\n");
+    psImage *sumFringe = NULL; // Sum of the fringes
+    for (int i = 0; i < solution->nFringeFrames; i++) {
+        pmReadout *fringeRO = fringes->readouts->data[i]; // Fringe readout
+        psImage *fringe = fringeRO->image; // Fringe image
+        psTrace("ppImage", 5, "Scale for fringe component %d is %f\n",
+                i, solution->coeff->data.F32[i + 1]);
+        psBinaryOp(fringe, fringe, "*", psScalarAlloc(solution->coeff->data.F32[i + 1], PS_TYPE_F32));
+        if (!sumFringe) {
+            sumFringe = psImageCopy(NULL, fringe, PS_TYPE_F32);
+        } else {
+            psBinaryOp(sumFringe, sumFringe, "+", fringe);
+        }
+
+        psVector *md5 = psImageMD5(fringe); // md5 hash
+        psString md5string = psMD5toString(md5); // String
+        psFree(md5);
+        psStringPrepend(&md5string, "Fringe image %d (scale %.3f) MD5: ",
+                        i, solution->coeff->data.F32[i + 1]);
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                         md5string, "");
+        psFree(md5string);
+
+# if (0)
+	// write metadata header value
+	// XXX this is measured per cell, but we only have headers per chip
+	psString keyword = NULL;
+	psStringAppend (&keyword, "FRNG_%02dV", i);
+	psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "Fringe Amplitude", solution->coeff->data.F32[i + 1]);
+	psFree (keyword);
+
+	keyword = NULL;
+	psStringAppend (&keyword, "FRNG_%02dE", i);
+	psMetadataAddF32(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "Fringe Amplitude error", solution->coeffErr->data.F32[i + 1]);
+	psFree (keyword);
+# endif
+
+    }
+    if (expTime != 1.0) {
+        psBinaryOp(sumFringe, sumFringe, "*", psScalarAlloc(expTime, PS_TYPE_F32));
+    }
+
+    // Apply the correction to all readouts
+    psArray *readouts = science->readouts; // Array of science readouts
+    for (int i = 0; i < readouts->n; i++) {
+        pmReadout *readout = readouts->data[i]; // Readout of interest
+        if (!readout || !readout->data_exists) {
+            continue;
+        }
+
+        // XXX: Make generic, so subregions may be subtracted as well
+        if (!psBinaryOp(readout->image, readout->image, "-", sumFringe)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to subtract fringe.\n");
+            return false;
+        }
+
+	// measure residual fringe amplitude. results go to FRINGE.RESIDUALS
+	ppImageDetrendFringeMeasure (readout, fringes, true, options);
+    }
+    psFree(sumFringe);
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now, used for reporting
+    psString timeString = psTimeToISO(time); // String with time
+    psFree(time);
+    psStringPrepend(&timeString, "Fringe correction completed at ");
+    psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK,
+                     timeString, "");
+    psFree(timeString);
+
+    return sumFringe;
+}
+
+bool ppImageDetrendFringeApply (pmConfig *config, pmChip *chip, const pmFPAview *inputView, const ppImageOptions *options) {
+
+    pmCell *cell = NULL;
+
+    assert (options->doFringe); // do not call if not needed
+    assert (inputView->chip != -1);
+    assert (inputView->cell == -1);
+    assert (inputView->readout == -1);
+
+    pmFPAview *view = pmFPAviewAlloc(0); // View for local processing
+    *view = *inputView;
+
+    // select the reference chip
+    pmChip *fringe = pmFPAfileThisChip(config->files, view, "PPIMAGE.FRINGE");
+    if (!fringe) {
+	psError(PS_ERR_UNKNOWN, false, "missing fringe reference data.\n");
+	psFree (view);
+	return false;
+    }
+
+    // Solve the fringe system
+    if (!ppImageDetrendFringeSolve(chip, fringe, false, options)) {
+	psError(PS_ERR_UNKNOWN, false, "failed to solve the fringe system.\n");
+	psFree (view);
+	return false;
+    }
+
+    // Go back over the cells to apply the fringe correction
+    view->cell = view->readout = -1;
+    while ((cell = pmFPAviewNextCell(view, chip->parent, 1)) != NULL) {
+	if (!cell->process || !cell->file_exists) {
+	    continue;
+	}
+
+	// Apply the fringe correction
+	psTrace("ppImage", 3, "Applying fringe correction...\n");
+	pmCell *fringeCell = pmFPAfileThisCell(config->files, view, "PPIMAGE.FRINGE");
+	if (!fringeCell) {
+	    psError(PS_ERR_UNKNOWN, false, "missing fringe reference data.\n");
+	    psFree (view);
+	    return false;
+	}
+
+	// XXX for now, skip the video cells (cell->readouts->n > 1)
+	if (cell->readouts->n > 1) {
+	  psWarning ("Skipping Video Cell for ppImageDetrendFringeApply");
+	  continue;
+	}
+
+	if (!ppImageDetrendFringeGenerate(cell, fringeCell, options)) {
+	    psError(PS_ERR_UNKNOWN, false, "failed to apply fringe image.\n");
+	    psFree (view);
+	    return false;
+	}
+    }
+
+    // Solve the residual fringe system
+    if (!ppImageDetrendFringeSolve(chip, fringe, true, options)) {
+	psError(PS_ERR_UNKNOWN, false, "failed to solve the residual fringe system.\n");
+	psFree (view);
+	return false;
+    }
+
+    psFree (view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDetrendNonLinear.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDetrendNonLinear.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDetrendNonLinear.c	(revision 23312)
@@ -0,0 +1,114 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+bool ppImageDetrendNonLinearPolynomial(pmReadout *input, psMetadataItem *dataItem) {
+
+    // These are the polynomial coefficients
+    psVector *coeff = dataItem->data.V; // The coefficient vector
+    if (coeff->type.type != PS_TYPE_F64) {
+        psVector *temp = psVectorCopy(NULL, coeff, PS_TYPE_F64); // F64 version
+        psFree (coeff);
+        coeff = temp;
+    }
+    psPolynomial1D *correction = psPolynomial1DAlloc(coeff->n - 1, PS_POLYNOMIAL_ORD);
+    psFree(correction->coeff);
+    correction->coeff = psMemIncrRefCounter(coeff->data.F64);
+    pmNonLinearityPolynomial(input, correction);
+    psFree(coeff);
+    psFree(correction);
+    return true;
+}
+
+bool ppImageDetrendNonLinearLookup(pmReadout *input, psMetadataItem *dataItem) {
+
+    // This is a filename: lookup table
+    char *name = dataItem->data.V;       // Filename
+    psLookupTable *table = psLookupTableAlloc(name, "%f %f", 0);
+    if (psLookupTableRead(table) <= 0) {
+        psErrorStackPrint(stderr, "Unable to read non-linearity correction file "
+                          "%s --- ignored\n", name);
+        return false;
+    }
+#ifdef PRODUCTION
+    pmNonLinearityLookup(input, table);
+#else
+    psVector *influx = table->values->data[0];
+    psVector *outflux = table->values->data[1];
+    pmNonLinearityLookup(input, influx, outflux);
+#endif
+    psFree(table);
+    return true;
+}
+
+bool ppImageDetrendNonLinear(pmReadout *input, ppImageOptions *options) {
+
+    psMetadataItem *concept;
+    pmCell *cell = input->parent;
+
+    switch (options->nonLinearType) {
+      case PS_DATA_VECTOR:
+        ppImageDetrendNonLinearPolynomial (input, options->nonLinearData);
+        return true;
+
+      case PS_DATA_STRING:
+        ppImageDetrendNonLinearLookup (input, options->nonLinearData);
+        return true;
+
+      case PS_DATA_METADATA:
+        // XXX EAM: this is somewhat confusing : let's wrap in a function when i understand it
+
+        // Go looking for the value in the hierarchy
+        concept = psMetadataLookup(cell->concepts, options->nonLinearSource);
+        if (! concept) {
+            pmChip *chip = cell->parent;// Parent chip
+            concept = psMetadataLookup(chip->concepts, options->nonLinearSource);
+            if (! concept) {
+                pmFPA *fpa = chip->parent; // Parent FPA
+                concept = psMetadataLookup(fpa->concepts, options->nonLinearSource);
+                if (! concept) {
+                    psLogMsg("phase2", PS_LOG_WARN, "Unable to find value of concept %s "
+                             "for non-linearity correction --- ignored.\n", (char *)options->nonLinearSource);
+                    return false;
+                }
+            }
+        }
+
+        if (concept->type != PS_DATA_STRING) {
+            psLogMsg("phase2", PS_LOG_WARN, "Type for concept %p isn't STRING, as"
+                     " expected for non-linearity correction --- ignored.\n",
+                     concept);
+            return false;
+        }
+
+        // Get the value of the concept
+        psString conceptValue = concept->data.V;
+        psMetadata *folder = (psMetadata *)options->nonLinearData->data.V;
+        psMetadataItem *optionItem = psMetadataLookup(folder, conceptValue);
+        if (!optionItem) {
+            psLogMsg("phase2", PS_LOG_WARN, "Unable to find %s in NONLIN.DATA"
+                     " --- ignored.\n", conceptValue);
+            return false;
+        }
+
+        switch (optionItem->type) {
+          case PS_DATA_VECTOR:
+            ppImageDetrendNonLinearPolynomial (input, optionItem);
+            return true;
+          case PS_DATA_STRING:
+            ppImageDetrendNonLinearLookup (input, optionItem);
+            return true;
+          default:
+            psLogMsg("phase2", PS_LOG_WARN, "Non-linearity correction "
+                     "desired but unable to interpret NONLIN.DATA for %s"
+                     " --- ignored\n", conceptValue);
+            return false;
+        }
+      default:
+        psAbort("Invalid options->nonLinearType");
+    }
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDetrendReadout.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDetrendReadout.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDetrendReadout.c	(revision 23312)
@@ -0,0 +1,171 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+bool ppImageDetrendReadout(pmConfig *config, ppImageOptions *options, pmFPAview *view)
+{
+    // construct a view for the detrend images (which have only one readout)
+    pmFPAview *detview = pmFPAviewAlloc(0);
+    *detview = *view;
+    detview->readout = 0;
+
+    // find the currently selected readout
+    pmReadout *input = pmFPAfileThisReadout(config->files, view, "PPIMAGE.INPUT");
+
+    // Masking on the basis of pixel value needs to be done before anything else, so the values are pristine.
+    if (options->doMaskBuild) {
+        pmReadoutGenerateMask(input, options->satMask, options->badMask);
+    }
+    // apply the externally supplied mask to the input->mask pixels
+    if (options->doMask) {
+        pmReadout *mask = pmFPAfileThisReadout(config->files, detview, "PPIMAGE.MASK");
+        pmMaskBadPixels(input, mask, options->maskValue);
+    }
+
+# if 0
+    // Non-linearity correction
+    if (options->doNonLin) {
+        ppImageDetrendNonLinear(detrend->input, input, options);
+    }
+# endif
+
+    // set up the dark and bias
+    pmCell *dark = NULL;                // Multi-dark
+    pmReadout *oldDark = NULL;          // Old-fashioned dark
+    pmReadout  *bias = NULL;
+    if (options->doBias) {
+        bias = pmFPAfileThisReadout(config->files, detview, "PPIMAGE.BIAS");
+    }
+    if (options->doDark) {
+        bool mdok;                      // Status of MD lookup
+        psMetadata *recipe = psMetadataLookupPtr (&mdok, config->recipes, RECIPE_NAME);
+        assert(mdok && recipe);
+        if (psMetadataLookupBool(&mdok, recipe, "OLDDARK")) {
+            oldDark = pmFPAfileThisReadout(config->files, detview, "PPIMAGE.DARK");
+        } else {
+            dark = pmFPAfileThisCell(config->files, detview, "PPIMAGE.DARK");
+        }
+    }
+
+    // Bias, dark and overscan subtraction are all merged.
+    if (options->doBias || options->doOverscan) {
+        if (!pmBiasSubtract(input, options->overscan, bias, oldDark, view)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to subtract bias.");
+	    psFree(detview);
+            return false;
+        }
+    }
+
+    // Weight on the basis of pixel value needs to be done after the overscan has been subtracted
+    if (options->doVarianceBuild) {
+        // create the target mask and variance images
+        pmReadoutGenerateVariance(input, true);
+    }
+
+    if (options->doDark && dark) {
+        if (!pmDarkApply(input, dark, options->maskValue)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to subtract dark.");
+	    psFree(detview);
+            return false;
+        }
+    }
+
+    if (options->doRemnance) {
+        if (!pmRemnance(input, options->maskValue, options->badMask,
+                        options->remnanceSize, options->remnanceThresh)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to mask remnance.");
+	    psFree(detview);
+            return false;
+        }
+    }
+
+    // Shutter correction
+    if (options->doShutter) {
+        pmReadout *shutter = pmFPAfileThisReadout(config->files, detview, "PPIMAGE.SHUTTER");
+        if (!pmShutterCorrectionApply(input, shutter, pmConfigMaskGet("FLAT", config))) {
+	    psFree(detview);
+            return false;
+        }
+    }
+
+    // Flat-field correction (no options used?)
+    if (options->doFlat) {
+        pmReadout *flat = pmFPAfileThisReadout(config->files, detview, "PPIMAGE.FLAT");
+        if (!pmFlatField(input, flat, options->flatMask)) {
+	    psFree(detview);
+            return false;
+        }
+    }
+
+    // Normalization by a single (known) constant
+    bool mdok;                          // Status of MD lookup
+    float norm = psMetadataLookupF32(&mdok, config->arguments, "NORMALIZATION");
+    if (mdok && isfinite(norm) && norm != 1.0) {
+        pmHDU *hdu = pmHDUFromReadout(input); // HDU of interest
+        psString comment = NULL;        // Comment to add
+        psStringAppend(&comment, "Normalization: %f", norm);
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, comment, "");
+        psFree(comment);
+
+        psBinaryOp(input->image, input->image, "*", psScalarAlloc(norm, PS_TYPE_F32));
+    }
+
+# if (1)
+    // Normalization by per-class values
+    psMetadata *normlist = psMetadataLookupMetadata(&mdok, config->arguments, "NORMALIZATION.TABLE");
+    if (normlist) {
+	pmFPAfile *inputFile = psMetadataLookupPtr(&mdok, config->files, "PPIMAGE.INPUT");
+
+	// get the menu of class IDs
+        psMetadata *menu = psMetadataLookupMetadata(&mdok, inputFile->camera, "CLASSID"); 
+        if (!menu) {
+            psError(PS_ERR_IO, false, "Unable to find CLASSID metadata in camera configuration");
+	    psFree(detview);
+            return false;
+        }
+	// get the rule for class_id for the desired class
+        const char *rule = psMetadataLookupStr(&mdok, menu, options->normClass); 
+        if (!rule) {
+            psError(PS_ERR_IO, false, "Unable to find NORM.CLASS value %s in CLASSID in camera configuration", options->normClass);
+	    psFree(detview);
+            return false;
+        }
+	// get the class_id from the rule
+        char *classID = pmFPAfileNameFromRule(rule, inputFile, view);
+        if (!classID) {
+            psError(PS_ERR_IO, false, "error converting CLASSID rule %s to name\n", rule);
+	    psFree(detview);
+            return false;
+        }
+
+	// get normalization from the class_id
+	float norm = psMetadataLookupF32 (&mdok, normlist, classID);
+
+        pmHDU *hdu = pmHDUFromReadout(input); // HDU of interest
+        psString comment = NULL;        // Comment to add
+        psStringAppend(&comment, "Normalization: %f", norm);
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, comment, "");
+        psFree(comment);
+
+	// apply the normalization
+        psBinaryOp(input->image, input->image, "*", psScalarAlloc(norm, PS_TYPE_F32));
+
+	psFree (classID);
+    }
+# endif
+
+    if (options->doFringe) {
+        pmCell *fringe = pmFPAfileThisCell(config->files, detview, "PPIMAGE.FRINGE");
+        if (!ppImageDetrendFringeMeasure(input, fringe, false, options)) {
+	    psFree(detview);
+            return false;
+        }
+    }
+
+    ppImageMemoryDump("detrend");
+
+    psFree(detview);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDetrendRecord.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDetrendRecord.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageDetrendRecord.c	(revision 23312)
@@ -0,0 +1,80 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+static void detrendRecord(
+    bool doThis,                        // Are we supposed to do this?
+    psMetadata *target,                 // Target metadata
+    const pmConfig *config,             // Configuration
+    const pmFPAview *view,              // View to cell
+    const char *filename,               // Name of file
+    const char *name,                   // Name in header
+    const char *desc                    // Description
+    )
+{
+    psAssert(config, "Need configuration");
+    psAssert(view, "Need view to cell");
+    psAssert(name, "Need file name");
+
+    if (!doThis) {
+        return;
+    }
+
+    pmFPAfile *file = psMetadataLookupPtr(NULL, config->files, filename); // File of interest
+    psAssert(file, "Should be there");
+
+    if (target) {
+        psMetadataAddStr(target, PS_LIST_TAIL, name, 0, desc, file->filename);
+    }
+
+    pmCell *input = pmFPAfileThisCell(config->files, view, "PPIMAGE.INPUT"); // File we're processing
+    psAssert(input, "Should be there");
+    pmHDU *hdu = pmHDUGetHighest(input->parent->parent, input->parent, input);  // HDU for cell
+
+    // Strip off path and Nebulous bits
+    char *base = file->filename;        // Base name of file
+    for (char *new = base; (new = strpbrk(base, "/:")); base = new + 1); // No action
+
+    // We don't want multiple listings in the header saying the same thing, so make sure we haven't put the
+    // same entry there.  Usually (if the detrend and the image have the same file level) there'll only end up
+    // being one entry.
+    psString regexp = NULL;             // Regular expression
+    psStringAppend(&regexp, "^%s$", name);
+    psMetadataIterator *iter = psMetadataIteratorAlloc(hdu->header, PS_LIST_HEAD, regexp); // Iterator for hdr
+    psFree(regexp);
+    psMetadataItem *item;               // Item from iteration
+    bool found = false;
+    while (!found && (item = psMetadataGetAndIncrement(iter))) {
+        if (item->type == PS_DATA_STRING && strcmp(item->data.str, base) == 0) {
+            found = true;
+        }
+    }
+    psFree(iter);
+    if (!found) {
+        psMetadataAddStr(hdu->header, PS_LIST_TAIL, name, PS_META_DUPLICATE_OK, desc, base);
+    }
+
+    return;
+}
+
+
+bool ppImageDetrendRecord(pmCell *cell, const pmConfig *config, const ppImageOptions *options,
+                          const pmFPAview *view)
+{
+    psMetadata *detrend = psMetadataAlloc(); // Detrend information
+    psMetadataAddMetadata(cell->analysis, PS_LIST_TAIL, "DETREND", 0, "Detrend information", detrend);
+
+    detrendRecord(options->doMask, detrend, config, view, "PPIMAGE.MASK", "DETREND.MASK", "Mask filename");
+    detrendRecord(options->doBias, detrend, config, view, "PPIMAGE.BIAS", "DETREND.BIAS", "Bias filename");
+    detrendRecord(options->doDark, detrend, config, view, "PPIMAGE.DARK", "DETREND.DARK", "Dark filename");
+    detrendRecord(options->doShutter, detrend, config, view, "PPIMAGE.SHUTTER", "DETREND.SHUTTER",
+                  "Shutter correction filename");
+    detrendRecord(options->doFlat, detrend, config, view, "PPIMAGE.FLAT", "DETREND.FLAT", "Flat filename");
+    detrendRecord(options->doFringe, detrend, config, view, "PPIMAGE.FRINGE", "DETREND.FRINGE",
+                  "Fringe filename");
+
+    psFree (detrend);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageErrorCodes.c.in
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageErrorCodes.c.in	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageErrorCodes.c.in	(revision 23312)
@@ -0,0 +1,26 @@
+/*
+ * The line
+    { PPIMAGE_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+#include "pslib.h"
+#include "ppImageErrorCodes.h"
+
+void ppImageErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PPIMAGE_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PPIMAGE_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PPIMAGE_ERR_NERROR - PPIMAGE_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       p_psMemSetPersistent(tmp, true);
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageErrorCodes.dat
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageErrorCodes.dat	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageErrorCodes.dat	(revision 23312)
@@ -0,0 +1,10 @@
+#
+# This file is used to generate ppImageErrorClasses.h
+#
+BASE = 500		First value we use; lower values belong to psLib
+# these errors correspond to standard exit conditions
+ARGUMENTS               Incorrect arguments
+SYS                     System error
+CONFIG                  Problem in configure files
+PROG                    Programming error
+DATA                    invalid data
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageErrorCodes.h.in
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageErrorCodes.h.in	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageErrorCodes.h.in	(revision 23312)
@@ -0,0 +1,18 @@
+#if !defined(PPIMAGE_ERROR_CODES_H)
+#define PPIMAGE_ERROR_CODES_H
+/*
+ * The line
+ *  PPIMAGE_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PPIMAGE_ERR_BASE = 512,
+    PPIMAGE_ERR_${ErrorCode},
+    PPIMAGE_ERR_NERROR
+} ppImageErrorCode;
+
+void ppImageErrorRegister(void);
+
+#endif
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageFileCheck.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageFileCheck.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageFileCheck.c	(revision 23312)
@@ -0,0 +1,60 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+void ppImageFileCheck (pmConfig *config) {
+
+    // add the output names to the output-type files
+    psMetadataItem *item = NULL;
+    psMetadataIterator *iter = psMetadataIteratorAlloc (config->files, PS_LIST_HEAD, NULL);
+    while ((item = psMetadataGetAndIncrement (iter)) != NULL) {
+        pmFPAfile *file = item->data.V;
+        pmFPA *fpa = file->fpa;
+        fprintf (stderr, "file %s\n", file->name);
+        if (!fpa) {
+            fprintf (stderr, "  has no FPA\n");
+            continue;
+        }
+        if (fpa->hdu) {
+            if (fpa->hdu->images) fprintf (stderr, "  (%d,%d) images\n", -1, -1);
+            if (fpa->hdu->variances) fprintf (stderr, "  (%d,%d) variances\n", -1, -1);
+            if (fpa->hdu->masks) fprintf (stderr, "  (%d,%d) masks\n", -1, -1);
+            if (fpa->hdu->header) fprintf (stderr, "  (%d,%d) header\n", -1, -1);
+        } else {
+            // fprintf (stderr, "  has no fpa data (%d,%d)\n", -1, -1);
+        }
+        for (int i = 0; i < fpa->chips->n; i++) {
+            pmChip *chip = fpa->chips->data[i];
+            if (chip->hdu) {
+                if (chip->hdu->images) fprintf (stderr, "  (%d,%d) images\n", i, -1);
+                if (chip->hdu->variances) fprintf (stderr, "  (%d,%d) variances\n", i, -1);
+                if (chip->hdu->masks) fprintf (stderr, "  (%d,%d) masks\n", i, -1);
+                if (chip->hdu->header) fprintf (stderr, "  (%d,%d) header\n", i, -1);
+            } else {
+                // fprintf (stderr, "  has no chip data (%d,%d)\n", i, -1);
+            }
+            for (int j = 0; j < chip->cells->n; j++) {
+                pmCell *cell = chip->cells->data[j];
+                if (cell->hdu) {
+                    if (cell->hdu->images) fprintf (stderr, "  (%d,%d) images\n", i, j);
+                    if (cell->hdu->variances) fprintf (stderr, "  (%d,%d) variances\n", i, j);
+                    if (cell->hdu->masks) fprintf (stderr, "  (%d,%d) masks\n", i, j);
+                    if (cell->hdu->header) fprintf (stderr, "  (%d,%d) header\n", i, j);
+                } else {
+                    // fprintf (stderr, "  has no cell data (%d,%d)\n", i, j);
+                }
+                for (int k = 0; k < cell->readouts->n; k++) {
+                    pmReadout *readout = cell->readouts->data[k];
+                    if (readout) {
+                        if (readout->image) fprintf (stderr, "  (%d,%d,%d) image\n", i, j, k);
+                        if (readout->variance) fprintf (stderr, "  (%d,%d,%d) variance\n", i, j, k);
+                        if (readout->mask) fprintf (stderr, "  (%d,%d,%d) masks\n", i, j, k);
+                    }
+                }
+            }
+        }
+    }
+    psFree (iter);
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageLoop.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageLoop.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageLoop.c	(revision 23312)
@@ -0,0 +1,235 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+#define ESCAPE(MESSAGE) { \
+  psError(PS_ERR_UNKNOWN, false, MESSAGE); \
+  psFree(view); \
+  return false; \
+}
+
+bool ppImageLoop(pmConfig *config, ppImageOptions *options)
+{
+    psMetadata *stats = options->doStats ? psMetadataAlloc() : NULL; // Statistics to output
+    float timeDetrend = 0;              // Amount of time spent in detrend
+    float timePhot = 0;                 // Amount of time spent in photometry
+
+    bool status;                        // Status of MD lookup
+    pmFPAfile *input = psMetadataLookupPtr(&status, config->files, "PPIMAGE.INPUT");
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find input data!\n");
+        ppImageCleanup(config, options);
+        exit(PS_EXIT_PROG_ERROR);
+    }
+
+    pmConfigCamerasCull(config, NULL);
+    pmConfigRecipesCull(config, "PPIMAGE,PPSTATS,PSPHOT,MASKS,PSASTRO,JPEG");
+
+    pmFPAview *view = pmFPAviewAlloc(0);// View for level of interest
+    pmHDU *lastHDU = NULL;              // Last HDU that was updated
+
+    // files associated with the science image
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+        ESCAPE("load failure for FPA");
+    }
+
+    pmChip *chip;                       // Chip from FPA
+    while ((chip = pmFPAviewNextChip(view, input->fpa, 1)) != NULL) {
+        psLogMsg ("ppImageLoop", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) {
+            continue;
+        }
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+            ESCAPE("load failure for Chip");
+        }
+
+        psTimerStart(TIMER_DETREND);
+        pmCell *cell;                   // Cell from chip
+        while ((cell = pmFPAviewNextCell(view, input->fpa, 1)) != NULL) {
+            psLogMsg ("ppImageLoop", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) {
+                continue;
+            }
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                ESCAPE("load failure for Cell");
+            }
+
+            // Put version information into the header
+            pmHDU *hdu = pmHDUGetHighest(input->fpa, chip, cell);
+            if (hdu && hdu != lastHDU) {
+                ppImageVersionHeader(hdu->header);
+                lastHDU = hdu;
+            }
+
+            // XXX for now, skip the video cells (cell->readouts->n > 1)
+            if (cell->readouts->n > 1) {
+              psWarning ("Skipping Video Cell for ppImageDetrendReadout");
+              continue;
+            }
+
+            // process each of the readouts
+            pmReadout *readout;         // Readout from cell
+            while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                    ESCAPE("load failure for Readout");
+                }
+                if (!readout->data_exists) {
+                    continue;
+                }
+
+                // XXX set the options->*Mask values here (after the mask images have been loaded
+                // and before any of the value are used)
+                if (!ppImageSetMaskBits(config, options)) {
+                    ESCAPE("Unable to set bit masks");
+                }
+
+                // perform the detrend analysis
+                if (!ppImageDetrendReadout(config, options, view)) {
+                    ESCAPE("Unable to detrend readout");
+                }
+
+                // free detrend images potentially in use: MASK, BIAS, DARK, SHUTTER, FLAT
+                if (!ppImageDetrendFree (config, view)) {
+                    ESCAPE("Unable to free detrend images");
+                }
+            }
+
+            if (cell->data_exists) {
+                ppImageDetrendRecord(cell, config, options, view);
+            }
+            // free detrend images potentially in use: MASK, BIAS, DARK, SHUTTER, FLAT
+            if (!ppImageDetrendFree (config, view)) {
+                ESCAPE("Unable to free detrend images");
+            }
+        }
+        // free detrend images potentially in use: MASK, BIAS, DARK, SHUTTER, FLAT
+        if (!ppImageDetrendFree (config, view)) {
+            ESCAPE("Unable to free detrend images");
+        }
+
+        // Apply the fringe correction
+        if (options->doFringe) {
+            if (!ppImageDetrendFringeApply(config, chip, view, options)) {
+                ESCAPE("Unable to defringe");
+            }
+        }
+        // free detrend images potentially in use: MASK, BIAS, DARK, SHUTTER, FLAT
+        if (!ppImageFringeFree (config, view)) {
+            ESCAPE("Unable to free fringe images");
+        }
+
+        // measure various pixel-based statistics for this image
+        if (!ppImagePixelStats(config, stats, options, view)) {
+            ESCAPE("Unable to measures pixel stats for image");
+        }
+        if (!ppImageMosaicChip(config, options, view, "PPIMAGE.CHIP", "PPIMAGE.OUTPUT")) {
+            ESCAPE("Unable to mosaic chip");
+        }
+        timeDetrend += psTimerClear(TIMER_DETREND);
+
+
+        // we perform photometry on the readouts of this chip in the output
+        psTimerStart(TIMER_PHOT);
+        if (options->doPhotom) {
+            if (!ppImagePhotom(config, view)) {
+                ESCAPE("error running photometry.");
+            }
+        }
+        timePhot += psTimerClear(TIMER_PHOT);
+
+        // replace the masked pixels with the background level
+        if (options->doBG) {
+            if (!ppImageSubtractBackground(config, view, options)) {
+                ESCAPE("Unable to subtract background");
+            }
+        }
+
+        // these may be used by ppImageSubtractBackground.
+        // if these are defined as internal files, drop them here
+        status = true;
+        status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL");
+        status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKMDL.STDEV");
+        status &= pmFPAfileDropInternal (config->files, "PSPHOT.BACKGND");
+        if (!status) {
+            psError(PSPHOT_ERR_PROG, false, "trouble dropping internal files");
+            psFree (view);
+            return false;
+        }
+
+        // binning (used for display) must take place after the background is replaced, if desired
+        if (!ppImageRebinChip(config, view, options, "PPIMAGE.BIN1")) {
+            ESCAPE("Unable to bin chip (level 1).");
+        }
+        if (!ppImageRebinChip(config, view, options, "PPIMAGE.BIN2")) {
+            ESCAPE("Unable to bin chip (level 2).");
+        }
+
+        // Close cells (XXX shouldn't pmFPAfileClose iterate down as needed?)
+        view->cell = -1;
+        while ((cell = pmFPAviewNextCell(view, input->fpa, 1)) != NULL) {
+            if (!cell->process || !cell->file_exists) {
+                continue;
+            }
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+                ESCAPE("save failure for Cell");
+            }
+        }
+
+        // Close chip
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+            ESCAPE("save failure for Chip");
+        }
+    }
+
+    // generate the full-scale FPA mosaic
+    if (!ppImageMosaicFPA(config, options, "PPIMAGE.OUTPUT.FPA1", "PPIMAGE.BIN1")) {
+        ESCAPE("failure in FPA Mosaic (level 1)");
+    }
+    if (!ppImageMosaicFPA(config, options, "PPIMAGE.OUTPUT.FPA2", "PPIMAGE.BIN2")) {
+        ESCAPE("failure in FPA Mosaic (level 2)");
+    }
+
+    // we perform astrometry on all chips after sources have been detected
+    // this also performs the psastro file IO
+    if (options->doAstromChip || options->doAstromMosaic) {
+        if (!ppImageAstrom(config)) {
+            ESCAPE("error running astrometry.");
+        }
+    }
+
+    if (psTraceGetLevel("ppImage") >= 3) {
+        ppImageFileCheck(config);
+    }
+
+    // Calculate summary statistics from FPA Metadata
+    if (!ppImageMetadataStats(config, stats, options)) {
+        ESCAPE("Unable to determine FPA-level metadata statistics.");
+    }
+
+    // Output and Close FPA
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+        ESCAPE("save failure for FPA");
+    }
+    psFree(view);
+
+    // Dump configuration
+    psString dump_file = psMetadataLookupStr(&status, config->arguments, "DUMP_CONFIG");
+    if (dump_file) {
+        pmConfigDump(config, input->fpa, dump_file);
+    }
+
+    // Write out summary statistics
+    if (options->doStats) {
+        psMetadataAddF32(stats, PS_LIST_TAIL, "DT_DET", 0, "Time spent detrending (sec)", timeDetrend);
+        psMetadataAddF32(stats, PS_LIST_TAIL, "DT_PHOT", 0, "Time spent photometering (sec)", timePhot);
+        psMetadataAddF32(stats, PS_LIST_TAIL, "DT_TOTAL", 0, "Total time (sec)", psTimerMark(TIMER_TOTAL));
+        if (!ppImageStatsOutput(config, stats, options)) {
+            ESCAPE("Unable to write statistics file.");
+        }
+    }
+    psFree (stats);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageMemory.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageMemory.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageMemory.c	(revision 23312)
@@ -0,0 +1,38 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+
+#include "ppImage.h"
+
+//#define TESTING
+
+void ppImageMemoryDump(const char *description)
+{
+#ifndef TESTING
+    return;
+#else
+    psMemBlock **leaks = NULL;
+    int numLeaks = psMemCheckLeaks(0, &leaks, NULL, true);
+
+    static int num = 0;
+    psString filename = NULL;
+    psStringAppend(&filename, "%s_%d.txt", description, num++);
+    FILE *fp = fopen(filename, "w");
+    psFree(filename);
+
+    for (int i = 0; i < numLeaks; i++) {
+        psMemBlock *memBlock = leaks[i];
+        fprintf(fp, "%ld : %s at (%s:%d)  ID: %lu  Ref: %lu\n",
+                (unsigned long)memBlock->userMemorySize, memBlock->func, memBlock->file,
+                (int)memBlock->lineno, (unsigned long)memBlock->id, memBlock->refCounter);
+    }
+    psFree(leaks);
+
+    fclose(fp);
+
+    return;
+#endif
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageMetadataStats.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageMetadataStats.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageMetadataStats.c	(revision 23312)
@@ -0,0 +1,46 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+// calculate stats from headers and concepts
+bool ppImageMetadataStats(pmConfig *config, psMetadata *stats, const ppImageOptions *options)
+{
+    bool mdok;              // Status of MD lookup
+
+    if (!options->doStats) {
+        return true;
+    }
+
+    // Extract statistics from the last output fpa
+    pmFPAfile *outImage  = psMetadataLookupPtr(&mdok, config->files, "PPIMAGE.OUTPUT");
+    pmFPAfile *outPhotom = psMetadataLookupPtr(&mdok, config->files, "PSPHOT.OUTPUT");
+    pmFPAfile *outAstrom = psMetadataLookupPtr(&mdok, config->files, "PSASTRO.OUTPUT");
+
+    if (!outImage && !outPhotom && !outAstrom) {
+        psError(PS_ERR_UNEXPECTED_NULL, true,
+                "Unable to find any output file (PPIMAGE.OUTPUT, PSPHOT.OUTPUT, PSASTRO.OUTPUT).");
+        return false;
+    }
+
+    // get the latest output product available
+    pmFPAfile *output = outAstrom;
+    if (!output) {
+        output = outPhotom;
+    }
+    if (!output) {
+        output = outImage;
+    }
+
+    // extract stats for the complete fpa
+    pmFPAview *view = pmFPAviewAlloc(0);
+    if (!ppStatsMetadata(stats, output->fpa, view, options->maskValue, config)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate stats for image.");
+        psFree(view);
+        return false;
+    }
+
+    psFree(view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageMosaic.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageMosaic.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageMosaic.c	(revision 23312)
@@ -0,0 +1,77 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+bool ppImageMosaicChip(pmConfig *config, const ppImageOptions *options, const pmFPAview *view,
+                       const char *outFile, const char *inFile)
+{
+    bool status;                        // Status of MD lookup
+
+    pmFPAfile *in = psMetadataLookupPtr(&status, config->files, inFile); // Input file
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find required I/O file!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    pmFPAfile *out = psMetadataLookupPtr(&status, config->files, outFile); // Output file
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find required I/O file!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    pmChip *outChip = pmFPAviewThisChip(view, out->fpa);
+    pmChip *inChip = pmFPAviewThisChip(view, in->fpa);
+    if (!outChip->hdu && !outChip->parent->hdu) {
+        const char *name = psMetadataLookupStr(&status, in->fpa->concepts, "FPA.OBS"); // Name of FPA
+        pmFPAAddSourceFromView(out->fpa, name, view, out->format);
+    }
+
+    psTrace("pmChipMosaic", 5, "mosaic chip %s to %s (xbin,ybin: %d,%d to %d,%d)\n",
+            in->name, out->name, in->xBin, in->yBin, out->xBin, out->yBin);
+
+    // Mosaic the chip, making a deep copy.  This has the side effect of making the output
+    // image products pure trimmed images, but also increases the memory footprint.
+    status = pmChipMosaic(outChip, inChip, true, options->blankMask);
+    return status;
+}
+
+bool ppImageMosaicFPA (pmConfig *config, const ppImageOptions *options, const char *outFile,
+                       const char *inFile)
+{
+    bool status;                        // Status of MD lookup
+
+    pmFPAfile *in = psMetadataLookupPtr(&status, config->files, inFile); // Input file
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find required I/O file!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    pmFPAfile *out = psMetadataLookupPtr(&status, config->files, outFile); // Output file
+    if (!status) {
+        psErrorStackPrint(stderr, "Can't find required I/O file!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // XXX test printing of all concepts
+    #if 0
+    for (int i = 0; i < in->fpa->chips->n; i++) {
+        pmChip *chip = in->fpa->chips->data[i];
+        for (int j = 0; j < chip->cells->n; j++) {
+            pmCell *cell = chip->cells->data[j];
+            psMetadataPrint(stdout, cell->concepts, 2);
+        }
+    }
+    #endif
+
+    const char *name = psMetadataLookupStr(&status, in->fpa->concepts, "FPA.OBS"); // Name of FPA
+
+    pmFPAview *view = pmFPAviewAlloc(0);
+    pmFPAAddSourceFromView(out->fpa, name, view, out->format);
+    psFree(view);
+
+    psTrace ("pmFPAMosaic", 5, "mosaic fpa %s to %s (xbin,ybin: %d,%d to %d,%d)\n",
+             in->name, out->name, in->xBin, in->yBin, out->xBin, out->yBin);
+    return pmFPAMosaic(out->fpa, in->fpa, false, options->blankMask);
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageOptions.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageOptions.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageOptions.c	(revision 23312)
@@ -0,0 +1,287 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+static void imageOptionsFree(ppImageOptions *options)
+{
+    psFree(options->overscan);
+    psFree(options->nonLinearData);
+    psFree(options->nonLinearSource);
+}
+
+ppImageOptions *ppImageOptionsAlloc(void)
+{
+    ppImageOptions *options = psAlloc(sizeof(ppImageOptions));
+    psMemSetDeallocator(options, (psFreeFunc)imageOptionsFree);
+
+    // actions which ppImage should perform
+    options->doMaskBuild     = false;   // Build internal mask
+    options->doVarianceBuild   = false;   // Build internal variance
+    options->doMask          = false;   // Mask bad pixels
+    options->doNonLin        = false;   // Non-linearity correction
+    options->doOverscan      = false;   // Overscan subtraction
+    options->doBias          = false;   // Bias subtraction
+    options->doDark          = false;   // Dark subtraction
+    options->doRemnance      = false;   // Remnance masking
+    options->doShutter       = false;   // Shutter correction
+    options->doFlat          = false;   // Flat-field normalisation
+    options->doFringe        = false;   // Fringe subtraction
+    options->doPhotom        = false;   // Source identification and photometry
+    options->doAstromChip    = false;   // Astrometry (per-chip)
+    options->doAstromMosaic  = false;   // Astrometry (full-mosaic)
+    options->doStats         = false;   // Measure and save image statistics
+
+    // output files requested
+    options->BaseFITS        = false;   // create output image
+    options->BaseMaskFITS    = false;   // create output mask image
+    options->BaseVarianceFITS  = false;   // create output variance image
+
+    options->ChipFITS        = false;   // create output chip-mosaic image
+    options->ChipMaskFITS    = false;   // create output chip-mosaic mask image
+    options->ChipVarianceFITS  = false;   // create output chip-mosaic variance image
+
+    options->FPA1FITS        = false;   // create fpa-mosaic binned image (scale 1)
+    options->FPA2FITS        = false;   // create fpa-mosaic binned image (scale 2)
+    options->Bin1FITS        = false;   // create binned image (scale 1)
+    options->Bin2FITS        = false;   // create binned image (scale 2)
+    options->Bin1JPEG        = false;   // create jpeg of binned image (scale 1)
+    options->Bin2JPEG        = false;   // create jpeg of binned image (scale 2)
+
+    // default flags for various activities
+    options->maskValue       = 0x00;    // Default mask value (used to skip / ignore pixels)
+    options->satMask         = 0x00;    // Saturated pixels (supplied to pmReadoutGenerateMask)
+    options->badMask         = 0x00;    // Bad (low) pixels (supplied to pmReadoutGenerateMask)
+    options->flatMask        = 0x00;    // Bad flat pixels (supplied to pmFlatField)
+    options->blankMask       = 0x00;    // Blank (no data, cell gap) pixels (supplied to pmChipMosaic, pmFPAMosaic)
+    options->markValue       = 0x00;    // A safe bit for internal marking
+
+    // Non-linearity default options
+    options->nonLinearType   = 0;       // Type of non-linearity data (vector, string or metadata)
+    options->nonLinearData   = NULL;    // The non-linearity data
+    options->nonLinearSource = NULL;    // If the non-linearity data is a menu, this provides the key
+
+    // Overscan defaults
+    options->overscan        = NULL;    // Overscan options
+
+    // binning parameters
+    options->xBin1           = 16;      // x-binning, scale 1
+    options->yBin1           = 16;      // y-binning, scale 1
+    options->xBin2           = 16;      // x-binning, scale 2
+    options->yBin2           = 16;      // y-binning, scale 2
+
+    // Fringe defaults
+    options->fringeRej       = NAN;     // Fringe rejection limit
+    options->fringeIter      = 0;       // Fringe iterations
+    options->fringeKeep      = 1.0;     // Fringe keep fraction
+
+    // Remnance values
+    options->remnanceSize    = 30;      // Size for remnance detection
+    options->remnanceThresh  = 25.0;    // Threshold for remnance detection
+
+    // per-class normalization source
+    options->normClass       = NULL;    // per-class normalizations refer to this class
+
+    return options;
+}
+
+ppImageOptions *ppImageOptionsParse(pmConfig *config)
+{
+    bool status;
+    ppImageOptions *options = ppImageOptionsAlloc ();
+
+    // select the recipe for this analysis
+    bool mdStatus = false;              // Result of MD lookup
+    psMetadata *recipe = psMetadataLookupMetadata(&mdStatus, config->recipes, RECIPE_NAME);
+    if (! mdStatus || !recipe) {
+        psLogMsg("ppImage", PS_LOG_ERROR, "Can't find recipe %s in the RECIPES.\n", RECIPE_NAME);
+        exit(EXIT_FAILURE);
+    }
+
+    // Non-linearity recipe options
+    if (psMetadataLookupBool(NULL, recipe, "NONLIN")) {
+        psMetadataItem *dataItem = psMetadataLookup(recipe, "NONLIN.DATA");
+        if (! dataItem) {
+            psLogMsg(__func__, PS_LOG_ERROR, "Non-linearity correction desired, but unable to "
+                     "find NONLIN.DATA in recipe %s.", RECIPE_NAME);
+            exit(EXIT_FAILURE);
+        }
+
+        options->doNonLin = true;
+        options->nonLinearType = dataItem->type;
+        options->nonLinearData = dataItem;
+
+        switch (dataItem->type) {
+            // No immediate action required
+          case PS_DATA_VECTOR:
+          case PS_DATA_STRING:
+            break;
+
+            // This is a menu; we need the key
+          case PS_DATA_METADATA:
+            {
+                bool status;
+                options->nonLinearSource = psMetadataLookupStr(&status, recipe, "NONLIN.SOURCE");
+                if (! status || ! options->nonLinearSource) {
+                    psLogMsg(__func__, PS_LOG_ERROR, "Non-linearity correction desired, but unable to "
+                            "find NONLIN.SOURCE in recipe %s.", RECIPE_NAME);
+                    exit(EXIT_FAILURE);
+                }
+            }
+            break;
+          default:
+            psLogMsg(__func__, PS_LOG_ERROR, "Non-linearity correction desired, but "
+                    "NONLIN.DATA is of invalid type in recipe %s.", RECIPE_NAME);
+            exit(EXIT_FAILURE);
+        }
+    }
+
+    // XXX PAP: The overscan stuff needs to be updated following the reworked API
+
+    // Overscan recipe options
+    // XXX EAM : we should abort on invalid options. default options?
+    if (psMetadataLookupBool(NULL, recipe, "OVERSCAN")) {
+        options->doOverscan = true;
+
+        // Do the overscan as a single value?
+        bool overscanSingle = psMetadataLookupBool(NULL, recipe, "OVERSCAN.SINGLE");
+
+        // How do we fit it?
+        pmFit overscanFit = PM_FIT_NONE; // Fit type for overscan
+        int overscanOrder = 0;          // Order for overscan fit
+        psString fit = psMetadataLookupStr(NULL, recipe, "OVERSCAN.FIT");
+        if (! strcasecmp(fit, "POLYNOMIAL")) {
+            overscanFit = PM_FIT_POLY_ORD;
+            overscanOrder = psMetadataLookupS32(NULL, recipe, "OVERSCAN.ORDER");
+        } else if (! strcasecmp(fit, "CHEBYSHEV")) {
+            overscanFit = PM_FIT_POLY_CHEBY;
+            overscanOrder = psMetadataLookupS32(NULL, recipe, "OVERSCAN.ORDER");
+        } else if (! strcasecmp(fit, "SPLINE")) {
+            overscanFit = PM_FIT_SPLINE;
+        } else if (strcasecmp(fit, "NONE")) {
+            psLogMsg(__func__, PS_LOG_WARN,
+                     "OVERSCAN.FIT (%s) in recipe %s is not one of NONE, POLYNOMIAL, or SPLINE",
+                     fit, RECIPE_NAME);
+            exit(EXIT_FAILURE);
+        }
+
+        // What method do we use to measure the overscan statistics?
+        // XXX allow user to specify psStats types by name
+        psStats *overscanStats = NULL;  // Statistics for overscan
+        psString stat = psMetadataLookupStr(NULL, recipe, "OVERSCAN.STAT");
+        if (! strcasecmp(stat, "MEAN")) {
+            // overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+            overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+        } else if (! strcasecmp(stat, "MEDIAN")) {
+            overscanStats = psStatsAlloc(PS_STAT_SAMPLE_MEDIAN);
+        } else {
+            psErrorStackPrint(stderr, "OVERSCAN.STAT (%s) in recipe %s is not one of MEAN or MEDIAN",
+                              stat, RECIPE_NAME);
+            exit(EXIT_FAILURE);
+        }
+
+        int boxcar = psMetadataLookupS32(NULL, recipe, "OVERSCAN.BOXCAR");
+        float gauss = psMetadataLookupF32(NULL, recipe, "OVERSCAN.GAUSS");
+
+        // Fill in the options
+        options->overscan = pmOverscanOptionsAlloc(overscanSingle, overscanFit, overscanOrder,
+                                                   overscanStats, boxcar, gauss);
+
+        options->overscan->constant = psMetadataLookupBool(NULL, recipe, "OVERSCAN.CONSTANT");
+        options->overscan->value = psMetadataLookupF32(NULL, recipe, "OVERSCAN.VALUE");
+
+        psFree(overscanStats);
+    }
+
+    // for these images, even if not required otherwise
+    options->doMaskBuild = psMetadataLookupBool(NULL, recipe, "MASK.BUILD");
+    options->doVarianceBuild = psMetadataLookupBool(NULL, recipe, "VARIANCE.BUILD");
+
+    // Mask recipe options (note that mask bit values are set in ppImageSetMaskBits.c)
+    options->doMask = psMetadataLookupBool(NULL, recipe, "MASK");
+
+    options->doBias = psMetadataLookupBool(NULL, recipe, "BIAS");
+    options->doDark = psMetadataLookupBool(NULL, recipe, "DARK");
+    options->doRemnance = psMetadataLookupBool(NULL, recipe, "REMNANCE");
+    options->doFlat = psMetadataLookupBool(NULL, recipe, "FLAT");
+    options->doFringe = psMetadataLookupBool(NULL, recipe, "FRINGE");
+    options->doShutter = psMetadataLookupBool(NULL, recipe, "SHUTTER");
+
+    options->doStats = false;
+    char *statsName = psMetadataLookupStr(&status, config->arguments, "STATS"); // Filename for statistics
+    if (statsName) {
+        options->doStats = true;
+    }
+
+    // binned image options
+    options->xBin1 = psMetadataLookupS32(&status, recipe, "BIN1.XBIN");
+    if (!status) {
+        psWarning("BIN1.XBIN not found in recipe: setting to default value.\n");
+    }
+    options->yBin1 = psMetadataLookupS32(&status, recipe, "BIN1.YBIN");
+    if (!status) {
+        psWarning("BIN1.YBIN not found in recipe: setting to default value.\n");
+        options->yBin1 = 16;
+    }
+
+    options->xBin2 = psMetadataLookupS32(&status, recipe, "BIN2.XBIN");
+    if (!status) {
+        psWarning("BIN2.XBIN not found in recipe: setting to default value.\n");
+       options->xBin1 = 16;
+    }
+    options->yBin2 = psMetadataLookupS32(&status, recipe, "BIN2.YBIN");
+    if (!status) {
+        psWarning("BIN2.YBIN not found in recipe: setting to default value.\n");
+        options->yBin1 = 16;
+    }
+
+    options->BaseFITS       = psMetadataLookupBool(NULL, recipe, "BASE.FITS");
+    options->BaseMaskFITS   = psMetadataLookupBool(NULL, recipe, "BASE.MASK.FITS");
+    options->BaseVarianceFITS = psMetadataLookupBool(NULL, recipe, "BASE.VARIANCE.FITS");
+
+    options->ChipFITS       = psMetadataLookupBool(NULL, recipe, "CHIP.FITS");
+    options->ChipMaskFITS   = psMetadataLookupBool(NULL, recipe, "CHIP.MASK.FITS");
+    options->ChipVarianceFITS = psMetadataLookupBool(NULL, recipe, "CHIP.VARIANCE.FITS");
+
+    options->FPA1FITS       = psMetadataLookupBool(NULL, recipe, "FPA1.FITS");
+    options->FPA2FITS       = psMetadataLookupBool(NULL, recipe, "FPA2.FITS");
+
+    options->Bin1FITS       = psMetadataLookupBool(NULL, recipe, "BIN1.FITS");
+    options->Bin1JPEG       = psMetadataLookupBool(NULL, recipe, "BIN1.JPEG");
+    options->Bin2FITS       = psMetadataLookupBool(NULL, recipe, "BIN2.FITS");
+    options->Bin2JPEG       = psMetadataLookupBool(NULL, recipe, "BIN2.JPEG");
+
+    options->doPhotom       = psMetadataLookupBool(NULL, recipe, "PHOTOM");
+    options->doAstromChip   = psMetadataLookupBool(NULL, recipe, "ASTROM.CHIP");
+    options->doAstromMosaic = psMetadataLookupBool(NULL, recipe, "ASTROM.MOSAIC");
+    options->doBG           = psMetadataLookupBool(NULL, recipe, "BACKGROUND");
+
+    // even if not requested explicitly, if any of these are set, build an internal mask and variance:
+    if (options->doBias || options->doOverscan || options->doDark || options->doShutter || options->doFlat ||
+        options->doPhotom) {
+        options->doMaskBuild = true;
+        options->doVarianceBuild = true;
+    } else if (options->doMask || options->doBG) {
+        options->doMaskBuild = true;
+    }
+
+    if ((options->doAstromChip || options->doAstromMosaic) && !options->doPhotom) {
+        psLogMsg(__func__, PS_LOG_ERROR, "Invalid PPIMAGE options: cannot do ASTROMetry without PHOTOMetry");
+        exit(EXIT_FAILURE);
+    }
+
+    // Fringe options
+    options->fringeRej = psMetadataLookupF32(NULL, recipe, "FRINGE.REJ");
+    options->fringeIter = psMetadataLookupS32(NULL, recipe, "FRINGE.ITER");
+    options->fringeKeep = psMetadataLookupF32(NULL, recipe, "FRINGE.KEEP");
+
+    // Remnance options
+    options->remnanceSize = psMetadataLookupF32(NULL, recipe, "REMNANCE.SIZE");
+    options->remnanceThresh = psMetadataLookupS32(NULL, recipe, "REMNANCE.THRESH");
+
+    // per-class normalization source (just a reference; don't free)
+    options->normClass = psMetadataLookupStr(NULL, recipe, "NORM.CLASS");
+
+    return options;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageParseCamera.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageParseCamera.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageParseCamera.c	(revision 23312)
@@ -0,0 +1,465 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+ppImageOptions *ppImageParseCamera(pmConfig *config)
+{
+    bool status = false;
+
+    if (!ppImageDefineFile(config, NULL, "PPIMAGE.INPUT", "INPUT", PM_FPA_FILE_IMAGE, PM_DETREND_TYPE_NONE)) {
+        psError(PS_ERR_IO, false, "Can't find an input image source");
+        return NULL;
+    }
+    pmFPAfile *input = psMetadataLookupPtr(NULL, config->files, "PPIMAGE.INPUT"); // Input file
+    psAssert(input, "We just put it there!");
+
+    // add recipe options supplied on command line
+    psMetadata *recipe  = psMetadataLookupPtr(&status, config->recipes, RECIPE_NAME);
+
+    // parse the options from the metadata format to the ppImageOptions structure
+    ppImageOptions *options = ppImageOptionsParse(config);
+
+    // the following are defined from the argument list, if given,
+    // otherwise they revert to the config information
+    // not all input or output images are used in a given recipe
+    if (options->doBias) {
+        if (!ppImageDefineFile(config, input->fpa, "PPIMAGE.BIAS", "BIAS",
+                               PM_FPA_FILE_IMAGE, PM_DETREND_TYPE_BIAS)) {
+            psError(PS_ERR_IO, false, "Can't find a bias image source");
+            psFree(options);
+            return NULL;
+        }
+    }
+    if (options->doDark) {
+        if (!ppImageDefineFile(config, input->fpa, "PPIMAGE.DARK", "DARK",
+                               PM_FPA_FILE_DARK, PM_DETREND_TYPE_DARK)) {
+            psError(PS_ERR_IO, false, "Can't find a dark image source");
+            psFree(options);
+            return NULL;
+        }
+    }
+    if (options->doMask) {
+        if (!ppImageDefineFile(config, input->fpa, "PPIMAGE.MASK", "MASK",
+                               PM_FPA_FILE_MASK, PM_DETREND_TYPE_MASK)) {
+            psError(PS_ERR_IO, false, "Can't find a mask image source");
+            psFree(options);
+            return NULL;
+        }
+
+#if 0
+        // I think this is now done automatically in the pmFPAfileDefine and pmFPAfileIOChecks -- PAP.
+
+        // XXX have ppImageDefineFile return the pmFPAfile?
+        pmFPAfile *mask = psMetadataLookupPtr(&status, config->files, "PPIMAGE.MASK");
+        psAssert(mask, "Just defined the mask!");
+
+        // Need to read the names of bit masks from the mask header and set them in the
+        // recipe.  If we are loading this from the detrend db, this action will happen
+        // when the file is resolved.
+        if (!mask->detrend) {
+            // XXX need to load the mask bit names from one of the headers
+            // this grabs the first available hdu : no guarantee that it will be valid, though
+            pmHDU *hdu = pmHDUGetFirst(mask->fpa);
+            if (!hdu) {
+                psError(PS_ERR_IO, true, "no valid HDU for PPIMAGE.INPUT.MASK");
+                return NULL;
+            }
+            // XXX is this consistent with the pmConfigMaskReadHeader call above?
+            if (!pmConfigMaskReadHeader(config, hdu->header)) {
+                psError(PS_ERR_IO, false, "error in mask bits");
+                return NULL;
+            }
+        }
+#endif
+    }
+    if (options->doShutter) {
+        if (!ppImageDefineFile(config, input->fpa, "PPIMAGE.SHUTTER", "SHUTTER",
+                               PM_FPA_FILE_IMAGE, PM_DETREND_TYPE_SHUTTER)) {
+            psError(PS_ERR_IO, false, "Can't find a shutter image source");
+            psFree(options);
+            return NULL;
+        }
+    }
+
+    if (options->doFlat) {
+        if (!ppImageDefineFile(config, input->fpa, "PPIMAGE.FLAT", "FLAT",
+                               PM_FPA_FILE_IMAGE, PM_DETREND_TYPE_FLAT)) {
+            psError(PS_ERR_IO, false, "Can't find a flat image source");
+            psFree(options);
+            return NULL;
+        }
+    }
+
+    int nThreads = psMetadataLookupS32(&status, config->arguments, "NTHREADS");
+    if (nThreads > 0) {
+        int nScanRows = psMetadataLookupS32(&status, recipe, "SCAN.ROWS");
+        pmDetrendSetThreadTasks(nScanRows);
+    }
+
+    // fringe frame are only applied for a subset of the filters.
+    // if the filter is one of those identified by a FRINGE.FILTERS metadata entry
+    // in ppImage.config, apply the fringe frame
+    if (options->doFringe) {
+        // determine filter from the concepts
+        const char *filter = psMetadataLookupStr(&status, input->fpa->concepts, "FPA.FILTER");
+        if (!status || !filter || strlen(filter) == 0) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find FPA.FILTER.\n");
+            psFree(options);
+            return NULL;
+        }
+
+        // select FRINGE.FILTERS from the recipe and test if the filter matches
+        // is the mdi saved with psList or string?
+        psMetadataItem *mdi = psMetadataLookup (recipe, "FRINGE.FILTERS");
+        if (mdi == NULL) {
+            // no valid filters for fringe data for this camera
+            options->doFringe = false;
+            goto skip_fringe;
+        }
+        // place entry on a list regardless of type
+        psList *filters = NULL;
+        if (mdi->type == PS_DATA_STRING) {
+            filters = psListAlloc(NULL);
+            psListAdd (filters, PS_LIST_HEAD, mdi);
+        } else if (mdi->type == PS_DATA_METADATA_MULTI) {
+            filters = psMemIncrRefCounter(mdi->data.list);
+        } else {
+            psError(PS_ERR_BAD_PARAMETER_TYPE, true,
+                    "FRINGE.FILTERS in recipe %s is not of type METADATA", RECIPE_NAME);
+            psFree(options);
+            return NULL;
+        }
+
+        // search through list to find the current filter
+        psListIterator *iter = psListIteratorAlloc (filters, PS_LIST_HEAD, FALSE);
+        options->doFringe = false;
+        for (int i = 0; !options->doFringe && (i < filters->n); i++) {
+            psMetadataItem *item = psListGetAndIncrement (iter);
+            char *validFilter = item->data.V;
+            if (strcmp (validFilter, filter)) continue;
+            options->doFringe = true;
+        }
+        psFree(iter);
+        psFree(filters);
+    }
+skip_fringe:
+    if (options->doFringe) {
+        if (!ppImageDefineFile(config, input->fpa, "PPIMAGE.FRINGE", "FRINGE",
+                               PM_FPA_FILE_FRINGE, PM_DETREND_TYPE_FRINGE)) {
+            psError (PS_ERR_IO, false, "Can't find a fringe image source");
+            return NULL;
+        }
+    }
+
+    // the following files are output targets
+    pmFPAfile *outImage = pmFPAfileDefineOutput(config, input->fpa, "PPIMAGE.OUTPUT");
+    if (!outImage) {
+        psError(PS_ERR_IO, false, _("Unable to generate output file from PPIMAGE.OUTPUT"));
+        psFree(options);
+        return NULL;
+    }
+    if (outImage->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT is not of type IMAGE");
+        psFree(options);
+        return NULL;
+    }
+    pmFPAfile *outMask = pmFPAfileDefineOutput(config, input->fpa, "PPIMAGE.OUTPUT.MASK");
+    if (!outMask) {
+        psError(PS_ERR_IO, false, _("Unable to generate output file from PPIMAGE.OUTPUT.MASK"));
+        psFree(options);
+        return NULL;
+    }
+    if (outMask->type != PM_FPA_FILE_MASK) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT.MASK is not of type MASK");
+        psFree(options);
+        return NULL;
+    }
+    pmFPAfile *outVariance = pmFPAfileDefineOutput(config, input->fpa, "PPIMAGE.OUTPUT.VARIANCE");
+    if (!outVariance) {
+        psError(PS_ERR_IO, false, _("Unable to generate output file from PPIMAGE.OUTPUT.VARIANCE"));
+        psFree(options);
+        return NULL;
+    }
+    if (outVariance->type != PM_FPA_FILE_VARIANCE) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT.VARIANCE is not of type VARIANCE");
+        psFree(options);
+        return NULL;
+    }
+
+    // XXX should these be bound explicitly to the outImage->fpa rather than the input->fpa?
+    pmFPAfile *chipImage = pmFPAfileDefineChipMosaic(config, input->fpa, "PPIMAGE.CHIP");
+    if (!chipImage) {
+        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.CHIP"));
+        psFree(options);
+        return NULL;
+    }
+    if (chipImage->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "PPIMAGE.CHIP is not of type IMAGE");
+        psFree(options);
+        return NULL;
+    }
+    pmFPAfile *chipMask = pmFPAfileDefineOutput(config, chipImage->fpa, "PPIMAGE.CHIP.MASK");
+    if (!chipMask) {
+        psError(PS_ERR_IO, false, _("Unable to generate output file from PPIMAGE.CHIP.MASK"));
+        psFree(options);
+        return NULL;
+    }
+    if (chipMask->type != PM_FPA_FILE_MASK) {
+        psError(PS_ERR_IO, true, "PPIMAGE.CHIP.MASK is not of type MASK");
+        psFree(options);
+        return NULL;
+    }
+    pmFPAfile *chipVariance = pmFPAfileDefineOutput(config, chipImage->fpa, "PPIMAGE.CHIP.VARIANCE");
+    if (!chipVariance) {
+        psError(PS_ERR_IO, false, _("Unable to generate output file from PPIMAGE.CHIP.VARIANCE"));
+        psFree(options);
+        return NULL;
+    }
+    if (chipVariance->type != PM_FPA_FILE_VARIANCE) {
+        psError(PS_ERR_IO, true, "PPIMAGE.CHIP.VARIANCE is not of type VARIANCE");
+        psFree(options);
+        return NULL;
+    }
+
+    pmFPAfile *byFPA1 = pmFPAfileDefineFPAMosaic(config, input->fpa, "PPIMAGE.OUTPUT.FPA1");
+    if (!byFPA1) {
+        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.OUTPUT.FPA1"));
+        psFree(options);
+        return NULL;
+    }
+    if (byFPA1->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT.FPA1 is not of type IMAGE");
+        psFree(options);
+        return NULL;
+    }
+    pmFPAfile *byFPA2 = pmFPAfileDefineFPAMosaic(config, input->fpa, "PPIMAGE.OUTPUT.FPA2");
+    if (!byFPA2) {
+        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.OUTPUT.FPA2"));
+        psFree(options);
+        return NULL;
+    }
+    if (byFPA2->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT.FPA2 is not of type IMAGE");
+        psFree(options);
+        return NULL;
+    }
+
+    // chipImage    -> psphotInput  (pmFPAfileDefineFromFile)       : fpa is constructed
+    // psphotInput  -> psphotOutput (pmFPAfileDefineOutputFromFile) : fpa is equated
+    // psphotOutput -> psastroInput (pmFPAfileDefineInput)          : fpa is ref-copied
+    // psastroInput -> psastroModel (pmFPAfileDefineFromArgs        : fpa is ref-copied
+    // psastroInput -> psastroModel (pmFPAfileDefineFromConf        : fpa is constructed
+    // psastroInput -> psastroModel (pmFPAfileDefineFromDetDB       : fpa is constructed (pmDetrendSelect uses input concepts )
+
+    // For photometry, we operate on the chip-mosaicked image
+    // we create a copy of the mosaicked image for psphot so we can write out a clean image
+    if (options->doPhotom || options->doBG) {
+
+        // this file is just used as a carrier; output files (eg, PSPHOT.RESID) are defined by
+        // psphotDefineFiles
+        pmFPAfile *psphotInput = pmFPAfileDefineFromFile (config, chipImage, 1, 1, "PSPHOT.INPUT");
+        PS_ASSERT (psphotInput, false);
+
+        // define associated psphot input/output files
+        if (!psphotDefineFiles (config, psphotInput)) {
+            psError(PSPHOT_ERR_CONFIG, false,
+                    "Trouble defining the additional input/output files for psphot");
+            return false;
+        }
+    }
+
+    // For photometry, we operate on the chip-mosaicked image
+    if (options->doAstromChip || options->doAstromMosaic) {
+        if (!options->doPhotom) {
+            psError(PSASTRO_ERR_CONFIG, false,
+                    "Photometry mode is not selected; it is required for astrometry");
+            return false;
+        }
+
+        pmFPAfile *psphotOutput = psMetadataLookupPtr(&status, config->files, "PSPHOT.OUTPUT");
+        PS_ASSERT(psphotOutput, false);
+
+        pmFPAfile *psastroInput = pmFPAfileDefineInput(config, psphotOutput->fpa, NULL, "PSASTRO.INPUT");
+        PS_ASSERT(psastroInput, false);
+        psastroInput->mode = PM_FPA_MODE_REFERENCE;
+
+        // define associated psphot input/output files
+        if (!psastroDefineFiles(config, psastroInput)) {
+            psError(PSPHOT_ERR_CONFIG, false,
+                    "Trouble defining the additional input/output files for psastro");
+            return false;
+        }
+
+        // deactivate the psastro files, reactive when needed
+        pmFPAfileActivate(config->files, false, "PSASTRO.OUTPUT");
+    }
+
+    // save any of these files?
+    outImage->save   = options->BaseFITS;
+    outMask->save    = options->BaseMaskFITS;
+    outVariance->save  = options->BaseVarianceFITS;
+
+    chipImage->save  = options->ChipFITS;
+    chipMask->save   = options->ChipMaskFITS;
+    chipVariance->save = options->ChipVarianceFITS;
+
+    byFPA1->save     = options->FPA1FITS;
+    byFPA2->save     = options->FPA2FITS;
+
+    // outImage is used as a carrier: input to chipImage -> require the data to remain at the CHIP level
+    outImage->freeLevel = PS_MIN(outImage->freeLevel, PM_FPA_LEVEL_CHIP);
+    outImage->dataLevel = outImage->freeLevel;
+    outImage->fileLevel = PS_MIN(outImage->fileLevel, outImage->dataLevel);
+
+    // outMask and outVariance must be freed at the same level as outImage (all freed by pmFPAFreeData)
+    outMask->freeLevel   = outImage->freeLevel;
+    outVariance->freeLevel = outImage->freeLevel;
+    outMask->dataLevel   = outImage->dataLevel;
+    outVariance->dataLevel = outImage->dataLevel;
+
+    // Ditto for the chip-mosaicked version
+    chipMask->freeLevel   = chipImage->freeLevel;
+    chipVariance->freeLevel = chipImage->freeLevel;
+    chipMask->dataLevel   = chipImage->dataLevel;
+    chipVariance->dataLevel = chipImage->dataLevel;
+
+    // the input data is the same as the outImage data : force the free levels to match
+    input->freeLevel = PS_MIN(outImage->freeLevel, input->freeLevel);
+
+    // define the binned target files (which may just be carriers for some camera configurations)
+    pmFPAfile *bin1 = pmFPAfileDefineFromFPA(config, chipImage->fpa, options->xBin1, options->yBin1,
+                                             "PPIMAGE.BIN1");
+    if (!bin1) {
+        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.BIN1"));
+        psFree(options);
+        return NULL;
+    }
+    if (bin1->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT.BIN1 is not of type IMAGE");
+        psFree(options);
+        return NULL;
+    }
+
+    pmFPAfile *bin2 = pmFPAfileDefineFromFPA(config, chipImage->fpa, options->xBin2, options->yBin2,
+                                             "PPIMAGE.BIN2");
+    if (!bin2) {
+        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.BIN2"));
+        psFree(options);
+        return NULL;
+    }
+    if (bin2->type != PM_FPA_FILE_IMAGE) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT.BIN2 is not of type IMAGE");
+        psFree(options);
+        return NULL;
+    }
+
+    // bin1 and bin2 are used as carriers: input for byFPA1, byFPA2
+    bin1->freeLevel = PM_FPA_LEVEL_FPA;
+    bin2->freeLevel = PM_FPA_LEVEL_FPA;
+
+    pmFPAfile *jpg1 = pmFPAfileDefineOutput(config, byFPA1->fpa, "PPIMAGE.JPEG1");
+    if (!jpg1) {
+        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.JPEG1"));
+        psFree(options);
+        return NULL;
+    }
+    if (jpg1->type != PM_FPA_FILE_JPEG) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT.JPEG1 is not of type JPEG");
+        psFree(options);
+        return NULL;
+    }
+    pmFPAfile *jpg2 = pmFPAfileDefineOutput(config, byFPA2->fpa, "PPIMAGE.JPEG2");
+    if (!jpg2) {
+        psError(PS_ERR_IO, false, _("Unable to generate new file from PPIMAGE.JPEG2"));
+        psFree(options);
+        return NULL;
+    }
+    if (jpg2->type != PM_FPA_FILE_JPEG) {
+        psError(PS_ERR_IO, true, "PPIMAGE.OUTPUT.JPEG2 is not of type JPEG");
+        psFree(options);
+        return NULL;
+    }
+
+    // XXX we could potentially not define these pmFPAfiles if no output is requested...
+    bin1->save = options->Bin1FITS;
+    bin2->save = options->Bin2FITS;
+    jpg1->save = options->Bin1JPEG;
+    jpg2->save = options->Bin2JPEG;
+
+    // Chip selection: turn on only the chips specified (pass status to suppress missing-key log msg)
+    char *chipLine = psMetadataLookupStr(&status, config->arguments, "CHIP_SELECTIONS");
+    psArray *chips = psStringSplitArray(chipLine, ",", false);
+    if (chips->n > 0) {
+        pmFPASelectChip (input->fpa, -1, true); // deselect all chips
+        for (int i = 0; i < chips->n; i++) {
+            int chipNum = atoi(chips->data[i]);
+            if (! pmFPASelectChip(input->fpa, chipNum, false)) {
+                psError(PS_ERR_IO, false, "Chip number %d doesn't exist in camera.\n", chipNum);
+                psFree(options);
+                return false;
+            }
+        }
+    }
+    psFree (chips);
+
+    if (psMetadataLookupBool(NULL, config->arguments, "INPUT_IS_FRINGE")) {
+        // It's a fringe file, so change the file type
+        input->type = PM_FPA_FILE_FRINGE;
+        outImage->type = PM_FPA_FILE_FRINGE;
+    }
+    if (psMetadataLookupBool(NULL, config->arguments, "INPUT_IS_DARK")) {
+        // It's a dark file, so change the file type
+        input->type = PM_FPA_FILE_DARK;
+        outImage->type = PM_FPA_FILE_DARK;
+        // Turn off compression --- there are just too many nasties that can happen
+        psFree(outImage->compression);
+        outImage->compression = NULL;
+        psFree(outImage->options);
+        outImage->options = NULL;
+    }
+
+    // Turn off mask and variance output if we're not doing anything interesting
+    if (!options->doMaskBuild && outMask->save) {
+        psWarning("output mask image (BASE.MASK.FITS) requested, but not generated: skipping.\n");
+        outMask->save = false;
+    }
+    if (!options->doVarianceBuild && outVariance->save) {
+        psWarning("output variance image (BASE.VARIANCE.FITS) requested, but not generated: skipping.\n");
+        outVariance->save = false;
+    }
+    if (!options->doMaskBuild && chipMask->save) {
+        psWarning("output mask image (CHIP.MASK.FITS) requested, but not generated: skipping.\n");
+        chipMask->save = false;
+    }
+    if (!options->doVarianceBuild && chipVariance->save) {
+        psWarning("output variance image (CHIP.VARIANCE.FITS) requested, but not generated: skipping.\n");
+        chipVariance->save = false;
+    }
+
+    if (psTraceGetLevel("ppImage.config") > 0) {
+        // Get a look inside all the files.
+        psMetadataIterator *filesIter = psMetadataIteratorAlloc(config->files, PS_LIST_HEAD, NULL);
+        psMetadataItem *item;               // Item from iteration
+        fprintf(stderr, "Files:\n");
+        while ((item = psMetadataGetAndIncrement(filesIter))) {
+            pmFPAfile *file = item->data.V; // File of interest
+            fprintf(stderr, "%s: %p %p %p (%p) %p\n", file->name,
+                    file->src, file->fpa,
+                    file->camera, file->fpa->camera, file->format);
+        }
+        psFree(filesIter);
+    }
+
+    // Change the input dark type between the old (IMAGE) and new (multi-DARK).
+    // Hopefully this is a temporary change until we all move over to using the new dark types
+    bool mdok;                          // Status of MD lookup
+    if (options->doDark && psMetadataLookupBool(&mdok, recipe, "OLDDARK")) {
+        pmFPAfile *dark = psMetadataLookupPtr(NULL, config->files, "PPIMAGE.DARK");
+        assert(dark);
+        dark->type = PM_FPA_FILE_IMAGE;
+    }
+
+    return (options);
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImagePhotom.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImagePhotom.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImagePhotom.c	(revision 23312)
@@ -0,0 +1,61 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+# include "ppImage.h"
+
+// In this function, we perform the psphot analysis routine for the chip-mosaicked images
+bool ppImagePhotom (pmConfig *config, pmFPAview *view) {
+
+    bool status;
+    pmCell *cell;
+    pmReadout *readout;
+
+    psphotInit ();
+
+    // find or define a pmFPAfile PSPHOT.INPUT
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSPHOT.INPUT");
+    if (!status) {
+        psError(PSPHOT_ERR_CONFIG, false, "PSPHOT.INPUT I/O file is not defined");
+        return false;
+    }
+
+    // we make a new copy of the output chip to keep psphot from modifying the output image
+    pmChip *oldChip = pmFPAviewThisChip (view, input->src);
+    pmChip *newChip = pmFPAviewThisChip (view, input->fpa);
+    pmChipCopy (newChip, oldChip);
+
+    // iterate over the cells and readout for this chip
+    while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+        psLogMsg ("ppImagePhotom", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+        if (! cell->process || ! cell->file_exists) { continue; }
+
+        // process each of the readouts
+        while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+            if (! readout->data_exists) { continue; }
+
+            // run the actual photometry analysis
+            if (!psphotReadout (config, view)) {
+                psError(psErrorCodeLast(), false, "failure in psphotReadout for chip %d, cell %d, readout %d\n", view->chip, view->cell, view->readout);
+                return false;
+            }
+
+	    // we want to save the MASK as modified by psphot, but not the data or weight
+	    // free the old mask and replace with a memory copy of the new mask
+	    pmReadout *oldReadout = pmFPAviewThisReadout (view, input->src);
+	    pmReadout *newReadout = pmFPAviewThisReadout (view, input->fpa);
+	    psFree (oldReadout->mask);
+	    oldReadout->mask = psMemIncrRefCounter (newReadout->mask);
+        }
+    }
+
+    ppImageMemoryDump("photom");
+
+    // the PSPHOT.INPUT file is a temporary file used to carry PPIMAGE.CHIP to psphotReadout
+    // XXX not sure that this is needed...
+//    pmFPAfileActivate (config->files, false, "PSPHOT.INPUT");
+
+    return true;
+}
+
+// XXX do we need to deactivate all files and activate the psphot ones explicitly?
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImagePixelStats.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImagePixelStats.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImagePixelStats.c	(revision 23312)
@@ -0,0 +1,85 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+// calculate stats, including MD5
+bool ppImagePixelStats(pmConfig *config, psMetadata *stats, const ppImageOptions *options,
+                       const pmFPAview *inputView)
+{
+    bool mdok;              // Status of MD lookup
+
+    // loop over the cells/readouts for this chip
+    pmFPAview *view = pmFPAviewAlloc(0); // View for local processing
+    *view = *inputView;
+
+    // perform the analysis of for this FPA
+    pmFPAfile *output = psMetadataLookupPtr(&mdok, config->files, "PPIMAGE.OUTPUT");
+    if (!output) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find output file (PPIMAGE.OUTPUT).");
+        psFree(view);
+        return false;
+    }
+
+    // select the corresponding chip
+    pmChip *chip = pmFPAviewThisChip(view, output->fpa);
+    if (!chip) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find requested chip.");
+        psFree(view);
+        return false;
+    }
+
+    // Perform statistics for this chip
+    if (options->doStats) {
+        if (!ppStatsPixels(stats, output->fpa, view, options->maskValue, config)) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate stats for image.");
+            psFree(view);
+            return false;
+        }
+
+        // extract the fringe amplitude from the analysis
+        if (options->doFringe && !ppStatsFringe(stats, chip, "FRINGE", "FRINGE.SOLUTION")) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to extract fringe solution for image.");
+            psFree(view);
+            return false;
+        }
+
+        // extract the fringe residual amplitude from the analysis
+        if (options->doFringe && !ppStatsFringe(stats, chip, "FRINGE_RESID", "FRINGE.RESIDUAL.SOLUTION")) {
+            psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to extract fringe solution for image.");
+            psFree(view);
+            return false;
+        }
+    }
+
+
+    view->cell = view->readout = -1;
+    pmCell *cell = NULL;                // Cell from iteration
+    while ((cell = pmFPAviewNextCell(view, output->fpa, 1)) != NULL) {
+        if (!cell->process || !cell->file_exists) {
+            continue;
+        }
+
+        // Add MD5 information for cell
+        pmHDU *hdu = pmHDUFromCell(cell); // HDU that owns the cell
+        pmReadout *readout = NULL;      // Readout from iteration
+        while ((readout = pmFPAviewNextReadout(view, output->fpa, 1)) != NULL) {
+            const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME");
+            const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME");
+
+            psString headerName = NULL; // Header name for MD5
+            psStringAppend(&headerName, "MD5_%s_%s_%d", chipName, cellName, view->readout);
+
+            psVector *md5 = psImageMD5(readout->image); // md5 hash
+            psString md5string = psMD5toString(md5); // String
+            psFree(md5);
+            psMetadataAddStr(hdu->header, PS_LIST_TAIL, headerName, PS_META_REPLACE, "Image MD5", md5string);
+            psFree(md5string);
+            psFree(headerName);
+        }
+    }
+
+    psFree (view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageRebinReadout.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageRebinReadout.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageRebinReadout.c	(revision 23312)
@@ -0,0 +1,44 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+bool ppImageRebinChip (pmConfig *config, pmFPAview *view, ppImageOptions *options, char *outName) {
+
+    pmCell *cell;
+    pmReadout *inReadout, *outReadout;
+
+    pmFPAfile *outFile = psMetadataLookupPtr(NULL, config->files, outName);
+    if (outFile == NULL) return false;
+
+    // XXX double check that chip != -1?
+
+    pmChip *inChip  = pmFPAviewThisChip (view, outFile->src);
+    pmChip *outChip = pmFPAviewThisChip (view, outFile->fpa);
+    if (!pmChipCopyStructure (outChip, inChip, outFile->xBin, outFile->yBin)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to copy chip structure.");
+        return false;
+    }
+
+    while ((cell = pmFPAviewNextCell (view, outFile->src, 1)) != NULL) {
+        psLogMsg ("ppImageRebinChip", 5, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+        if (! cell->process || ! cell->file_exists) { continue; }
+
+        // process each of the readouts
+        while ((inReadout = pmFPAviewNextReadout (view, outFile->src, 1)) != NULL) {
+            if (! inReadout->data_exists) { continue; }
+
+            outReadout = pmFPAviewThisReadout (view, outFile->fpa);
+
+            // run the rebin code
+            if (!pmReadoutRebin(outReadout, inReadout, options->maskValue, outFile->xBin, outFile->yBin)) {
+                psError(PS_ERR_UNKNOWN, false, "Unable to rebin readout.");
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageReplaceBackground.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageReplaceBackground.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageReplaceBackground.c	(revision 23312)
@@ -0,0 +1,180 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+// In this function, we perform the psphot analysis routine for the chip-mosaicked images
+bool ppImageSubtractBackground(pmConfig *config, const pmFPAview *view, const ppImageOptions *options)
+{
+    psAssert(config, "Need configuration");
+    psAssert(view, "Need view to chip");
+    psAssert(options, "Need options");
+
+    if (!options->doBG) {
+        return true;
+    }
+
+    bool status;                        // Status of MD lookup
+    pmFPAfile *input = psMetadataLookupPtr(&status, config->files, "PPIMAGE.CHIP"); // File to correct
+    if (!status) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "PPIMAGE.CHIP file is not defined");
+        return false;
+    }
+
+    psMetadata *ppImageRecipe = psMetadataLookupPtr(NULL, config->recipes, RECIPE_NAME);
+    psAssert(ppImageRecipe, "Need PPIMAGE recipe");
+    psMetadata *psphotRecipe = psMetadataLookupPtr(NULL, config->recipes, PSPHOT_RECIPE);
+    psAssert(psphotRecipe, "Need PSPHOT recipe");
+
+    // XXX Should this be options->maskValue or options->maskValue & ~options->satMask?
+    //     The latter will leave saturated pixels high
+    psImageMaskType maskVal = options->maskValue;
+
+    // user-defined masks to test for good/bad pixels (build from recipe list if not yet set)
+    psMetadataAddImageMask(psphotRecipe, PS_LIST_TAIL, "MASK.PSPHOT", PS_META_REPLACE, "user-defined mask", maskVal);
+
+    // Since we are working on a chip-mosaicked image, there should only be a single cell and readout
+    pmChip *chip = pmFPAviewThisChip(view, input->fpa); // Chip of interest
+    if (!chip) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to find chip");
+        return false;
+    }
+    if (chip->cells->n == 0) {
+        psWarning("Chip has no cells");
+        return true;
+    }
+    if (chip->cells->n > 1) {
+        psWarning("Chip has %ld cells; only the first will be processed", chip->cells->n);
+    }
+    pmCell *cell = chip->cells->data[0]; // Cell of interest
+    if (!cell || !cell->process || !cell->file_exists) {
+        // Nothing to process
+        return true;
+    }
+    if (cell->readouts->n == 0) {
+        psWarning("Cell has no readouts");
+        return true;
+    }
+    if (cell->readouts->n > 1) {
+        psWarning("Cell has %ld readouts; only the first will be processed", cell->readouts->n);
+    }
+    pmReadout *ro = cell->readouts->data[0]; // Readout of interest
+    if (!ro || !ro->data_exists) {
+        // Nothing to process
+        return true;
+    }
+    psImage *image = ro->image, *mask = ro->mask; // Image and mask of interest
+
+    // View corresponding to this readout
+    pmFPAview roView = *view;
+    roView.cell = roView.readout = 0;
+
+    // If the background model file has not been defined, psphotModelBackground will generate it
+    pmReadout *modelRO = NULL;
+    pmFPAfile *modelFile = psMetadataLookupPtr(&status, config->files, "PSPHOT.BACKMDL"); // Background model
+    if (modelFile) {
+        modelRO = pmFPAviewThisReadout(&roView, modelFile->fpa); // Background model
+    }
+
+    // the background model has not been defined, or at least not generated
+    if (!modelFile || !modelRO) {
+        if (!psphotModelBackground(config, &roView, "PPIMAGE.CHIP")) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to model background");
+            return false;
+        }
+        // the model file should now at least be defined
+        modelFile = psMetadataLookupPtr(&status, config->files, "PSPHOT.BACKMDL"); // Background model
+        if (!modelFile) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to define background model I/O file");
+            return false;
+        }
+        // now grab the readout from the correct location:
+        if (modelFile->mode == PM_FPA_MODE_INTERNAL) {
+            modelRO = modelFile->readout;
+        } else {
+            modelRO = pmFPAviewThisReadout(&roView, modelFile->fpa);
+        }
+        if (!modelRO) {
+            psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find background model");
+            return false;
+        }
+    }
+    psImageBinning *binning = psMetadataLookupPtr(&status, modelRO->analysis,
+                                                  "PSPHOT.BACKGROUND.BINNING"); // Binning for model
+    if (!binning) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find background binning");
+        return false;
+    }
+
+# define USE_UNBIN 1
+# if (USE_UNBIN)
+    // select background pixels, from output background file, or create
+    pmReadout *background = NULL;
+    pmFPAfile *backfile = psMetadataLookupPtr (&status, config->files, "PSPHOT.BACKGND");
+    if (backfile) {
+        // we are using PSPHOT.BACKGND as an I/O file: select readout or create
+        if (backfile->mode == PM_FPA_MODE_INTERNAL) {
+            background = backfile->readout;
+        } else {
+            background = pmFPAviewThisReadout (&roView, backfile->fpa);
+        }
+        if (background == NULL) {
+            // readout does not yet exist: create from input
+            pmFPAfileCopyStructureView (backfile->fpa, input->fpa, 1, 1, &roView);
+            background = pmFPAviewThisReadout (&roView, backfile->fpa);
+            if ((image->numCols != background->image->numCols) || (image->numRows != background->image->numRows)) {
+                psError (PSPHOT_ERR_PROG, true, "inconsistent sizes for background dimensions");
+                return false;
+            }
+        }
+    } else {
+        background = pmFPAfileDefineInternal (config->files, "PSPHOT.BACKGND", image->numCols, image->numRows, PS_TYPE_F32);
+    }
+    psF32 **backData = background->image->data.F32;
+
+    // linear interpolation to full-scale
+    if (!psImageUnbin (background->image, modelRO->image, binning)) {
+        psError (PSPHOT_ERR_PROG, true, "inconsistent sizes for unbinning");
+        return false;
+    }
+
+    // Do the background subtraction
+    int numCols = image->numCols, numRows = image->numRows; // Size of image
+    for (int y = 0; y < numRows; y++) {
+        for (int x = 0; x < numCols; x++) {
+            float value = backData[y][x];
+            if (!isfinite(value)) {
+                image->data.F32[y][x] = NAN;
+                mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] |= options->badMask;
+            } else {
+                image->data.F32[y][x] -= value;
+            }
+        }
+    }
+# else
+    // Do the background subtraction
+    int numCols = image->numCols, numRows = image->numRows; // Size of image
+    for (int y = 0; y < numRows; y++) {
+        for (int x = 0; x < numCols; x++) {
+            if (mask && mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & maskVal) {
+                image->data.F32[y][x] = 0.0;
+            } else {
+                float value = psImageUnbinPixel(x + 0.5, y + 0.5, modelRO->image, binning); // Background value
+                if (!isfinite(value)) {
+                    image->data.F32[y][x] = NAN;
+                    mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] |= options->badMask;
+                } else {
+                    image->data.F32[y][x] -= value;
+                }
+            }
+        }
+    }
+# endif
+
+    // XXX should these really be here?? (probably not...)
+    // pmFPAfileDropInternal(config->files, "PSPHOT.BACKMDL");
+    // pmFPAfileDropInternal(config->files, "PSPHOT.BACKMDL.STDEV");
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageSetMaskBits.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageSetMaskBits.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageSetMaskBits.c	(revision 23312)
@@ -0,0 +1,46 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+bool ppImageSetMaskBits (pmConfig *config, ppImageOptions *options) {
+
+    if (!pmConfigMaskSetBits(&options->maskValue, &options->markValue, config)) {
+        psError (PS_ERR_UNKNOWN, true, "Unable to define the mask bit values");
+        return false;
+    }
+
+    // at this point we know we have valid values for required entries SAT, BAD, FLAT, BLANK:
+
+    // mask for non-linear flat corrections
+    options->flatMask = pmConfigMaskGet("FLAT", config);
+    psAssert (options->flatMask, "flat mask not set");
+
+    // mask for non-existent data  (default to DETECTOR if not defined)
+    options->blankMask = pmConfigMaskGet("BLANK", config);
+    psAssert (options->blankMask, "blank mask not set");
+
+    // mask for saturated data  (default to RANGE if not defined)
+    options->satMask = pmConfigMaskGet("SAT", config);
+    psAssert (options->satMask, "sat mask not set");
+
+    // mask for below-range data  (default to RANGE if not defined)
+    options->badMask = pmConfigMaskGet("BAD", config);
+    psAssert (options->badMask, "bad mask not set");
+
+    // save MASK and MARK on the PSPHOT recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+
+    // set maskValue and markValue in the psphot recipe
+    psMetadataAddImageMask(recipe, PS_LIST_TAIL, "MASK.PSPHOT", PS_META_REPLACE, "user-defined mask",
+                           options->maskValue);
+    psMetadataAddImageMask(recipe, PS_LIST_TAIL, "MARK.PSPHOT", PS_META_REPLACE, "user-defined mask",
+                           options->markValue);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageSetThreads.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageSetThreads.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageSetThreads.c	(revision 23312)
@@ -0,0 +1,26 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+bool ppImageThread_ppImageDetrendReadout (psThreadJob *job) {
+
+    pmConfig *config        = job->args->data[0];
+    ppImageOptions *options = job->args->data[1];
+    pmFPAview *view         = job->args->data[2];
+
+    bool status = ppImageDetrendReadout(config, options, view);
+    return status;
+}
+
+bool ppImageSetThreads () {
+
+    psThreadTask *task = NULL;
+
+    task = psThreadTaskAlloc ("PPIMAGE_DETREND_READOUT", 3);
+    task->function = &ppImageThread_ppImageDetrendReadout;
+    psThreadTaskAdd (task);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageStatsOutput.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageStatsOutput.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageStatsOutput.c	(revision 23312)
@@ -0,0 +1,34 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+// write stats to output file
+bool ppImageStatsOutput(pmConfig *config, psMetadata *stats, const ppImageOptions *options)
+{
+    bool mdok;
+
+    // measure statistics, or ignore?
+    if (!options->doStats) {
+        return true;
+    }
+
+    // get the output stats filename
+    const char *statsName = psMetadataLookupStr(&mdok, config->arguments, "STATS"); // Filename for statistics
+    if (!statsName && !strlen(statsName)) {
+        psError(PS_ERR_UNEXPECTED_NULL, false, "missing STATS entry in arguments list.");
+        return false;
+    }
+
+    // Write out
+    psString resolved = pmConfigConvertFilename(statsName, config, true, true); // Resolved filename
+    if (!psMetadataConfigWrite(stats, resolved)) {
+        psError(PS_ERR_IO, false, "Unable to serialize stats metadata.\n");
+        psFree(resolved);
+        return false;
+    }
+    psFree(resolved);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageVersion.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageVersion.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppImageVersion.c	(revision 23312)
@@ -0,0 +1,115 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "ppImage.h"
+
+#ifndef PPIMAGE_VERSION
+#error "PPIMAGE_VERSION is not set"
+#endif
+#ifndef PPIMAGE_BRANCH
+#error "PPIMAGE_BRANCH is not set"
+#endif
+#ifndef PPIMAGE_SOURCE
+#error "PPIMAGE_SOURCE is not set"
+#endif
+
+#define xstr(s) str(s)
+#define str(s) #s
+
+psString ppImageVersion(void)
+{
+    char *value = NULL;
+    psStringAppend(&value, "%s@%s", xstr(PPIMAGE_BRANCH), xstr(PPIMAGE_VERSION));
+    return value;
+}
+
+psString ppImageSource(void)
+{
+    return psStringCopy (xstr(PPIMAGE_SOURCE));
+}
+
+psString ppImageVersionLong(void)
+{
+    psString version = ppImageVersion();  // Version, to return
+    psString source = ppImageSource(); // Source
+
+    psStringPrepend(&version, "ppImage ");
+    psStringAppend(&version, " from %s, built %s, %s", source, __DATE__, __TIME__);
+    psFree(source);
+
+#ifdef __OPTIMIZE__
+    psStringAppend(&version, " optimised");
+#else
+    psStringAppend(&version, " unoptimised");
+#endif
+
+    return version;
+};
+
+bool ppImageVersionHeader(psMetadata *header)
+{
+    PS_ASSERT_METADATA_NON_NULL(header, false);
+
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psString history = NULL;               // History string
+    psStringAppend(&history, "ppImage at %s", timeString);
+    psFree(timeString);
+    psMetadataAddStr(header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, history);
+    psFree(history);
+
+    psLibVersionHeader(header);
+    psModulesVersionHeader(header);
+    psphotVersionHeader(header);
+    psastroVersionHeader(header);
+    ppStatsVersionHeader(header);
+
+    psString version = ppImageVersion(); // ppImage software version
+    psString source  = ppImageSource();  // ppImage software source
+
+    psStringPrepend(&version, "ppImage version: ");
+    psStringPrepend(&source, "ppImage source: ");
+
+    psMetadataAddStr(header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, version);
+    psMetadataAddStr(header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, source);
+
+    psFree(version);
+    psFree(source);
+
+    return true;
+}
+
+
+void ppImageVersionPrint(void)
+{
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psLogMsg("ppImage", PS_LOG_INFO, "ppImage at %s", timeString);
+    psFree(timeString);
+
+    psString pslib = psLibVersionLong();// psLib version
+    psString psmodules = psModulesVersionLong(); // psModules version
+    psString psphot = psphotVersionLong(); // psphot version
+    psString psastro = psastroVersionLong(); // psastro version
+    psString ppStats = ppStatsVersionLong(); // ppStats version
+    psString ppImage = ppImageVersionLong(); // ppImage version
+
+    psLogMsg("ppImage", PS_LOG_INFO, "%s", pslib);
+    psLogMsg("ppImage", PS_LOG_INFO, "%s", psmodules);
+    psLogMsg("ppImage", PS_LOG_INFO, "%s", psphot);
+    psLogMsg("ppImage", PS_LOG_INFO, "%s", psastro);
+    psLogMsg("ppImage", PS_LOG_INFO, "%s", ppStats);
+    psLogMsg("ppImage", PS_LOG_INFO, "%s", ppImage);
+
+    psFree(pslib);
+    psFree(psmodules);
+    psFree(psphot);
+    psFree(psastro);
+    psFree(ppStats);
+    psFree(ppImage);
+
+    return;
+}
Index: /branches/eam_branches/eam_branch_20090312/ppImage/src/ppTest.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/ppImage/src/ppTest.c	(revision 23312)
+++ /branches/eam_branches/eam_branch_20090312/ppImage/src/ppTest.c	(revision 23312)
@@ -0,0 +1,190 @@
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+
+#include "pslib.h"
+#include "psmodules.h"
+
+int main(int argc, char *argv[])
+{
+    psLibInit(NULL);
+
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// ppImageConfig.c
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    pmConfig *config = pmConfigRead(&argc, argv, "PPIMAGE");
+    if (! config) {
+        psErrorStackPrint(stderr, "Can't find site configuration!\n");
+        exit(EXIT_FAILURE);
+    }
+
+    // Parse other command-line arguments
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-key", 0, "exposure ID", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-bias", 0, "Name of the bias image", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-dark", 0, "Name of the dark image", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-flat", 0, "Name of the flat-field image", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-mask", 0, "Name of the mask image", "");
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-fringe", 0, "Name of the fringe image", "");
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "-chip", 0, "Chip number to process (if positive)", -1);
+
+    if (! psArgumentParse(config->arguments, &argc, argv) || argc != 3) {
+        printf("\nPan-STARRS Phase 2 processing\n\n");
+        printf("Usage: %s INPUT.fits OUTPUT.fits\n\n", argv[0]);
+        psArgumentHelp(config->arguments);
+        psFree(config->arguments);
+        exit(EXIT_FAILURE);
+    }
+
+    // Add the input and output images (which remain on the command-line) to the arguments list
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-input",  0, "Name of the input image", argv[1]);
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "-output", 0, "Name of the output image", argv[2]);
+
+    // Define database handle, if used
+#if 0
+    config->database = pmConfigDB(config->site);
+#endif
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// ppImageParseCamera.c extract with some alterations
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    const char *inName = psMetadataLookupStr(NULL, config->arguments, "-input");
+    psLogMsg("ppImage", PS_LOG_INFO, "Opening input image: %s\n", inName);
+    psFits *inFile = psFitsOpen(inName, "r"); // File handle for FITS file
+    if (! inFile) {
+        // There's no point in continuing if we can't open the input
+        psErrorStackPrint(stderr, "Can't open input image: %s\n", inName);
+        exit(EXIT_FAILURE);
+    }
+    psMetadata *phu = psFitsReadHeader(NULL, inFile); // FITS primary header
+
+    psMetadata *cameraFormat = pmConfigCameraFormatFromHeader(NULL, NULL, config, phu, true);
+    if (! config->camera) {
+        cameraFormat = pmConfigCameraFormatFromHeader(NULL, NULL, config, phu, true);
+        if (! config->camera) {
+             // There's no point in continuing if we can't recognise what we've got
+            psErrorStackPrint(stderr, "Can't find camera configuration!\n");
+            exit(EXIT_FAILURE);
+        }
+    }
+    // Determine the correct recipe to use
+    if (! config->recipes && !pmConfigReadRecipes(config, PM_RECIPE_SOURCE_CAMERA | PM_RECIPE_SOURCE_CL)) {
+        // There's no point in continuing if we can't work out what recipes to use
+        psErrorStackPrint(stderr, "Can't find recipe configuration!\n");
+        exit(EXIT_FAILURE);
+    }
+
+#if 1
+    const char *outName = psMetadataLookupStr(NULL, config->arguments, "-output");
+    psLogMsg("ppImage", PS_LOG_INFO, "Opening output image: %s\n", outName);
+    psFits *outFile = psFitsOpen(outName, "w");
+    if (!outFile) {
+        // There's no point in continuing if we can't open the output
+        psErrorStackPrint(stderr, "Can't open output image: %s\n", outName);
+        exit(EXIT_FAILURE);
+    }
+#endif
+
+    // Construct camera in preparation for reading
+    pmFPA *fpa = pmFPAConstruct(config->camera, config->cameraName);
+    pmFPAview *view = pmFPAAddSourceFromHeader(fpa, phu, cameraFormat);
+    printf("View chip: %d\n", view->chip);
+    printf("View cell: %d\n", view->cell);
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// The action happens here
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+#if 0
+    pmFPAPrint(stdout, fpa, false, false);
+    exit(0);
+#endif
+
+#if 0
+    // A mosaic
+    psMetadata *mosaicCamera = psMetadataConfigRead(NULL, NULL, "/home/mithrandir/price/ipp/config/mcshort_mosaic/camera.config", true);
+    pmFPA *mosaicFPA = pmFPAConstruct(mosaicCamera);
+    psMetadata *mosaicFormat = psMetadataConfigRead(NULL, NULL, "/home/mithrandir/price/ipp/config/mcshort_mosaic/format_mosaic.config", true);
+    pmFPAview *mosaicView = pmFPAviewAlloc(0);
+    pmFPAAddSourceFromView(mosaicFPA, mosaicView, mosaicFormat);
+    psFree(mosaicView);
+#endif
+
+    // Read the FPA
+    pmFPARead(fpa, inFile, NULL);
+
+#if 1
+    psArray *chips = fpa->chips;
+    pmFPAWrite(fpa, outFile, NULL, true, false);
+    for (int i = 0; i < chips->n; i++) {
+        pmChip *chip = chips->data[i];
+#if 1
+        //pmChipRead(chip, inFile, NULL);
+        pmChipWrite(chip, outFile, NULL, true, false);
+        psArray *cells = chip->cells;
+        for (int j = 0; j < cells->n; j++) {
+            pmCell *cell = cells->data[j];
+            pmCellWrite(cell, outFile, NULL, true);
+#if 0 // Read bit by bit
+            pmReadout *readout = pmReadoutAlloc(cell);
+            for (int z = 0; pmReadoutReadNext(readout, inFile, z, 512); z++) {
+                do {
+                    printf("Chip %d, Cell %d, Plane %d, row0 = %d\n", i, j, z, readout->row0);
+                    pmReadoutWriteNext(readout, outFile, z);
+                    //pmFPAPrint(stdout, fpa, false, true);
+                } while (pmReadoutReadNext(readout, inFile, z, 512));
+            }
+            psFree(readout);
+#else // Read the whole cell at once
+            //            pmCellRead(cell, inFile, NULL);
+            //            pmCellWrite(cell, outFile, NULL, false);
+            //            pmCellFreeData(cell);
+#endif
+        }
+        pmChipWrite(chip, outFile, NULL, false, false);
+#endif
+
+#if 0
+        pmChipMosaic(mosaicFPA->chips->data[i], chip);
+        pmChipWrite(mosaicFPA->chips->data[i], outFile, NULL, true, true);
+        pmChipFreeData(chip);
+        pmChipFreeData(mosaicFPA->chips->data[i]);
+#endif
+    }
+    pmFPAWrite(fpa, outFile, NULL, false, false);
+#endif
+
+#if 0
+    pmFPAMosaic(mosaicFPA, fpa);
+    pmFPAWrite(mosaicFPA, outFile, NULL, true, true);
+    //pmFPAPrint(stdout, mosaicFPA, true, true);
+    psFree(mosaicCamera);
+    psFree(mosaicFormat);
+    psFree(mosaicView);
+    //psFree(mosaicFPA);
+#endif
+
+#if 1
+    pmFPAPrint(stdout, fpa, false, false);
+#endif
+
+#if 1
+    psFitsClose(outFile);
+#endif
+
+    psFree(view);
+    psFree(phu);
+    psFree(fpa);
+    psFree(config);
+    psFitsClose(inFile);
+    psFree(cameraFormat);
+
+    pmConceptsDone();
+    pmConfigDone();
+    psLibFinalize();
+
+    // Pau.
+}
