Index: /branches/ccl_branches/ipponly-20191108/pswarp/Doxyfile.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/Doxyfile.in	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/Doxyfile.in	(revision 41091)
@@ -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           = pswarp
+
+# 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/ccl_branches/ipponly-20191108/pswarp/Makefile.am
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/Makefile.am	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/Makefile.am	(revision 41091)
@@ -0,0 +1,3 @@
+SUBDIRS = src
+
+CLEANFILES = *.pyc *~ core core.*
Index: /branches/ccl_branches/ipponly-20191108/pswarp/autogen.sh
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/autogen.sh	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/autogen.sh	(revision 41091)
@@ -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=pswarp
+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/ccl_branches/ipponly-20191108/pswarp/configure.ac
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/configure.ac	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/configure.ac	(revision 41091)
@@ -0,0 +1,51 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.61)
+
+AC_INIT([pswarp], [0.1.1], [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_STDLDFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC_C99
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE
+
+dnl ------------------------------------------------------------
+
+AC_PATH_PROG([ERRORCODES], [psParseErrorCodes], [missing])
+if test "$ERRORCODES" = "missing" ; then
+  AC_MSG_ERROR([psParseErrorCodes is required])
+fi
+
+
+PKG_CHECK_MODULES([PSLIB], [pslib >= 1.0.0])
+PKG_CHECK_MODULES([PSMODULE], [psmodules >= 1.0.0])
+PKG_CHECK_MODULES([PPSTATS], [ppStats >= 1.0.0]) 
+PKG_CHECK_MODULES([PSPHOT], [psphot >= 0.8.0]) 
+
+dnl Set CFLAGS for build
+IPP_STDOPTS
+IPP_STDCFLAGS
+
+echo "PSWARP_CFLAGS: $PSWARP_CFLAGS"
+echo "PSWARP_LIBS: $PSWARP_LIBS"
+
+IPP_VERSION
+
+AC_SUBST([PSWARP_CFLAGS])
+AC_SUBST([PSWARP_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  Doxyfile
+])
+
+AC_OUTPUT
Index: /branches/ccl_branches/ipponly-20191108/pswarp/doc/notes.20130406.txt
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/doc/notes.20130406.txt	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/doc/notes.20130406.txt	(revision 41091)
@@ -0,0 +1,108 @@
+
+20130406
+
+examining pswarp to handle inverse transformations or more general transformations:
+
+pswarp:
+  * setup / init functions (psLibInit, psphotInit, etc)
+  * pswarpArguments   : parse things like -file (name), -list (name), etc
+  * pswarpParseCamera : examine minimal files, define cameras & pmFPAfile + pmFPA
+    		        this function reads headers for files
+
+			uses pmFPAfileDefineFromArgs / pmFPAfileBindFromArgs for inputs
+			uses pswarpDefineSkycell for outputs
+
+			pmFPAfileDefineSkycell is used to generate the output
+			FPAs.  This forces the file to be FPA-level only, which is wrong. 
+			Not certain of the impact of replacing with pmFPAfileDefineOutput
+
+			pmFPAfileDefineSkycell:
+				* appends -SKYCELL to the camera name
+
+  * pswarpOptions     : check the recipe options, reconcile with cmd-line options
+
+  * pswarpDefine      : create output image pixels, update related concepts, 
+    		      	associate output astrometry (skycell) with output pmFPA structures
+			
+
+
+in pswarpParseCamera:
+  * pswarpDefineSkycell : this function is based on pmFPAfileDefineFromArgs
+    			* reads fileset from config->arguments (a fileset is an array of filenames)
+			* select first filename & translate to UNIX namespace
+			* open fits file
+			* read fits header (PHU)
+			* inherit some config elements from input config (user, system, files, arguments)
+			* determine format from header
+			  (NOTE: currently does not examine or set fileLevel)
+			* build the pmFPA structure based on camera & name
+			* bind filerule to fpa, generate the pmFPAfile
+			* sets file->fileLevel based on format, but slightly out of sync wrt pmFPAfileDefineFromArgs
+  			  (NOTE: does not expect / examine any other file)
+
+
+in pswarpLoop (major blocks):
+
+   * find the needed recipe / filerules / etc
+   * pswarpIOChecksBefore() : generate the outputs?  (isn't this done in pswarpDefine?)
+   * read input astrometry headers (also loads sources or not?)
+   * apply astrometry headers to input->fpa
+   * pswarpTransformReadout : transform pixels, etc
+   * pswarpPixelsLit : measure filling & masks stats
+   * modify the covariance
+   * modify the ZP & trimsec (seems like these should be encapsulated)
+   * update headers
+   * measure the PSF	
+   * measure pixel stats on output
+   * add MD5 to header
+   
+   
+
+----
+
+pswarp issues:
+
+1) operations need to loop over the output astrometry / targets for fpa/chip/cell levels
+   a) I replaced pswarpDefineSkycell with an update to pmFPAfileDefineXX which does the looping
+   b) is it identical in behavior to pswarpDefineSkycell?
+      * YES : I can drop pswarpDefineSkycell 
+
+2) how do we load the SOURCES?
+
+   * I need to load the headers for the astrometry, and I do this by
+     setting the type of the input astrometry to be WCS.  is this the
+     same as CMF, but the latter reads the sources??
+
+   * by changing the filerules entry CMF <-> WCS pswarpDefine.c either does or does not read the sources
+
+     (for testing, I want to use a skycell image or template to define the astrometry; this would not have sources to be read)
+
+3) does the I/O loop in pswarpLoop (pswarpIOChecksBefore) do anything useful?
+   ** I don't think so... this operation is done in pswarpDefine.
+
+4) how do I determine the overlaps correctly? (I'd like to do this before I load sources or pixels)
+
+   ** pswarpMapSetLocalModel does not seem to depend on astrometry model style (GOOD!)
+   
+-----
+
+Changes to support arbitrary fpa to arbitrary fpa:
+
+in pswarpDefine:
+ * load astrometry headers (source and target)
+ * determine overlap (source -> target)
+   * loop over target chips
+   * determine o
+ * activate output targets which will get data.
+ * generate output images
+
+in pswarpLoop
+ * need to loop over output targets as well as input sources...
+
+*** looks like I can select the correct output chips and write them.
+    Currently, the output format is inherited from the output
+    astrometry description.  that means we get a MEF output file with
+    one chip per extension.  that seems poor, but I think we can fix
+    this with the filerules.
+
+** trouble running warp -> chip using astrometry from skycell (ok using astrom from warp)
Index: /branches/ccl_branches/ipponly-20191108/pswarp/doc/notes.txt
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/doc/notes.txt	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/doc/notes.txt	(revision 41091)
@@ -0,0 +1,29 @@
+
+- pswarp takes an image (and corresponding mask and weight images) and
+  warps the pixels to match the specified skycell projection and pixel
+  grid.  the astrometry of the input image is supplied by the header
+  of the image or by a specified SMP file. 
+
+- the input image, weight, mask are loaded into pmFPA structures with
+  the layout determined by the camera/format/layout
+
+- the skycell is loaded into a pmFPA structure.  at a minimum, a
+  single skycell may contain a single chip, cell, and readout, which
+  in turn correspond to the single set of pixels representing the
+  image region.  
+
+  * is there a need or use for a skycell to have a multilayered
+    structure equivalent in any way to the fpa->chip->cell->readout
+    layer?
+
+    - perhaps we will want to use a single fpa for a larger region
+      with small sky cells representing contiguous pixel regions.  
+
+    - perhaps we will want to warp an entire fpa exposure at once and
+      use a skyfpa to represent all of the possible output skycells
+      for a single input fpa exposure.
+
+- the output is a file or set of files corresponding to the skycell
+  files.  the details should be specified by the PSWARP.OUTPUT file
+  rule.
+
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/Makefile.am
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/Makefile.am	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/Makefile.am	(revision 41091)
@@ -0,0 +1,63 @@
+bin_PROGRAMS = pswarp
+
+# Force recompilation of pswarpVersion.c, since it gets the version information
+pswarpVersion.c: pswarpVersionDefinitions.h
+pswarpVersionDefinitions.h: pswarpVersionDefinitions.h.in FORCE
+	pslib-setsvnversion.pl PSWARP pswarpVersionDefinitions.h.in pswarpVersionDefinitions.h
+FORCE: ;
+
+pswarp_CPPFLAGS = $(PSLIB_CFLAGS) $(PSMODULE_CFLAGS) $(PPSTATS_CFLAGS) $(PSPHOT_CFLAGS) $(PSWARP_CFLAGS)
+pswarp_LDFLAGS = $(PSLIB_LIBS) $(PSMODULE_LIBS) $(PPSTATS_LIBS) $(PSPHOT_LIBS) $(PSWARP_LIBS)
+
+pswarp_SOURCES = \
+	pswarp.c			\
+	pswarpArguments.c		\
+	pswarpExitCode.c		\
+	pswarpLoop.c			\
+	pswarpLoopBackground.c		\
+	pswarpCleanup.c			\
+	pswarpStatsFile.c		\
+	pswarpDefine.c			\
+	pswarpDefineBackground.c	\
+	pswarpDefineSkycell.c	        \
+	pswarpDefineLayout.c	        \
+	pswarpErrorCodes.c		\
+	pswarpLoadAstrometry.c          \
+	pswarpMakePSF.c			\
+	pswarpMapGrid.c			\
+	pswarpMaskStats.c               \
+	pswarpMatchRange.c		\
+	pswarpOverlaps.c		\
+	pswarpOptions.c	        	\
+	pswarpParseCamera.c		\
+	pswarpPixelsLit.c		\
+	pswarpSetMaskBits.c		\
+	pswarpSetThreads.c	        \
+	pswarpTransformReadout.c	\
+	pswarpTransformSources.c 	\
+	pswarpTransformTile.c		\
+	pswarpUpdateStatistics.c        \
+	pswarpUpdateMetadata.c          \
+	pswarpVersion.c            	\
+	pswarpFiles.c
+
+noinst_HEADERS = \
+	pswarp.h \
+	pswarpErrorCodes.h
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
+### Error codes.
+BUILT_SOURCES = pswarpErrorCodes.h pswarpErrorCodes.c
+CLEANFILES = pswarpErrorCodes.h pswarpErrorCodes.c
+
+pswarpErrorCodes.h : pswarpErrorCodes.dat pswarpErrorCodes.h.in
+	$(ERRORCODES) --data=pswarpErrorCodes.dat --outdir=. pswarpErrorCodes.h
+
+pswarpErrorCodes.c : pswarpErrorCodes.dat pswarpErrorCodes.c.in pswarpErrorCodes.h
+	$(ERRORCODES) --data=pswarpErrorCodes.dat --outdir=. pswarpErrorCodes.c
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarp.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarp.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarp.c	(revision 41091)
@@ -0,0 +1,60 @@
+/** @file pswarp.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.15 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-11 23:27:58 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "pswarp.h"
+
+int main (int argc, char **argv)
+{
+    psTimerStart("pswarp");
+
+    psLibInit(NULL);
+    pmModelClassInit();
+    psphotInit();
+
+    pmConfig *config = pswarpArguments(argc, argv);
+    if (!config) {
+        pswarpCleanup(config, NULL);
+    }
+
+    pswarpStatsFile *statsFile = pswarpStatsFileOpen (config);
+
+    pswarpVersionPrint();
+
+    // load identify the data sources
+    if (!pswarpParseCamera(config)) {
+        pswarpCleanup(config, statsFile);
+    }
+
+    if (!pswarpOptions(config)) {
+        pswarpCleanup(config, statsFile);
+    }
+
+    // load the input & output astrometry, find the output overlaps, generate the output pixels
+    if (!pswarpDefineLayout(config)) {
+        pswarpCleanup(config, statsFile);
+    }
+
+    // load input pixels & and warp
+    if (!pswarpLoop(config, statsFile->md)) {
+        pswarpCleanup(config, statsFile);
+    }
+
+    // load input pixels & and warp
+    if (!pswarpLoopBackground(config, statsFile->md)) {
+	pswarpCleanup(config, statsFile);
+    }
+
+    // output and free 
+    // NOTE: pswarpCleanup calls exit
+    psLogMsg("pswarp", PS_LOG_INFO, "complete pswarp run: %f sec\n", psTimerMark("pswarp"));
+    pswarpCleanup(config, statsFile);
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarp.h
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarp.h	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarp.h	(revision 41091)
@@ -0,0 +1,223 @@
+/** @file pswarp.h
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.18 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-06 03:10:36 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#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 <ppStats.h>
+
+#define THREADED 1
+
+#include "pswarpErrorCodes.h"
+#define PSWARP_RECIPE  "PSWARP" ///< Name of the recipe to use
+#define PSASTRO_RECIPE "PSASTRO" ///< Name of the recipe to use
+
+#define PSWARP_ANALYSIS_GOODPIX     "PSWARP.GOODPIX" ///< Name for number of good pixels in analysis metadata
+#define PSWARP_ANALYSIS_COVARIANCES "PSWARP.COVARIANCES" ///< Name for covariance matrices on analysis MD
+#define PSWARP_ANALYSIS_JACOBIAN    "PSWARP.JACOBIAN" ///< Name for Jacobian on analysis MD
+#define PSWARP_ANALYSIS_BACKMAPS    "PSWARP.BACKMAPS" ///< Name for array of backwarps maps on analysis MD
+#define PSWARP_ANALYSIS_CHIPNAMES   "PSWARP.CHIPNAMES" ///< Name for array of input chipnames on analysis MD
+#define PSWARP_ANALYSIS_CHIPREGIONS "PSWARP.CHIPREGIONS" ///< Name for array of input chipnames on analysis MD
+
+/**
+ * a single pswarpMap converts coordinates from one image to a second image
+ * the linear model is only valid over a limited range of pixels
+ */
+typedef struct {
+    double Xo, Xx, Xy;
+    double Yo, Yx, Yy;
+    int xo;
+    int yo;
+} pswarpMap;
+
+/* the pswarpMapGrid carries a collection of pswarpMag structures representing
+ * the local value of the pswarpMap at different locations in the image.
+ */
+typedef struct {
+    pswarpMap ***maps;
+    int nXpts, nYpts;                   ///< number of x,y samples in the grid
+    int nXpix, nYpix;                   ///< x,y spacing in src image pixels of grid samples
+    double xMin,  yMin;                 ///< coordinate of first grid sample
+} pswarpMapGrid;
+
+typedef struct {
+    /** values which are common to all tilesa */
+    pmReadout *input;
+    pmReadout *output;
+    pswarpMapGrid *grid;
+    psImageInterpolation *interp;
+    psImage *region;
+
+    /** values which are needed to control the background model warping. */
+    bool background_warping;
+    double offset_x;
+    double offset_y;
+  
+    /** input values for this tile */
+    int gridX;
+    int gridY;
+
+    /** output values for this tile */
+    long goodPixels;                    ///< Number of good pixels
+    int xMin, xMax, yMin, yMax;         ///< Bounds of tile
+    psKernel *covariance;               ///< Covariance matrix
+    double jacobian;                    ///< (Square root of) local Jacobian
+} pswarpTransformTileArgs;
+
+typedef struct {
+    FILE *f;				// File stream for statistics
+    char *name;				// Filename for statistics
+    psMetadata *md;			// Container for statistics
+} pswarpStatsFile;
+
+pswarpTransformTileArgs *pswarpTransformTileArgsAlloc(void);
+bool pswarpTransformTile (pswarpTransformTileArgs *args);
+
+pmConfig *pswarpArguments (int argc, char **argv);
+bool pswarpOptions(pmConfig *config);
+bool pswarpSetMaskBits (pmConfig *config);
+bool pswarpParseCamera (pmConfig *config);
+bool pswarpDefine (pmConfig *config);
+bool pswarpDefineBackground (pmConfig *config);
+bool pswarpLoop (pmConfig *config, psMetadata *stats);
+bool pswarpLoopSkycell (pmConfig *config, psMetadata *stats);
+bool pswarpLoopBackground (pmConfig *config, psMetadata *stats);
+psExit pswarpExitCode(psExit exitValue);
+bool pswarpTransformReadout (pmReadout *output, pmReadout *input, pmConfig *config, bool backgroundWarp);
+bool pswarpTransformSources(pmReadout *output, pmReadout *input, pmConfig *config);
+
+bool pswarpMatchRange (int *minX, int *minY, int *maxX, int *maxY, pmReadout *dest, pmReadout *src);
+
+pswarpMap *pswarpMapAlloc (void);
+pswarpMapGrid *pswarpMapGridAlloc (int Nx, int Ny);
+
+pswarpMapGrid *pswarpMapGridFromImage (pmReadout *dest, pmReadout *src, int nXpix, int nYpix);
+bool pswarpMapGridSetGrid (pswarpMapGrid *grid, int ix, int iy, int *gridX, int *gridY);
+bool pswarpMapGridCoordRange (pswarpMapGrid *grid, int gridX, int gridY, psPlane *min, psPlane *max);
+int pswarpMapGridNextGrid_X (pswarpMapGrid *grid, int gridX);
+int pswarpMapGridNextGrid_Y (pswarpMapGrid *grid, int gridY);
+double pswarpMapGridMaxError (pswarpMapGrid *grid);
+bool pswarpMapApply (double *outX, double *outY, pswarpMap *map, double inX, double inY);
+bool pswarpMapSetLocalModel (pswarpMap *map, pmReadout *dest, pmReadout *src, int ix, int iy);
+
+bool pswarpDefineSkycell (pmFPAfile **outFile, pmConfig **outConfig, pmConfig *config,
+                          const char *filename, const char *argname);
+
+/// Get the range of lit pixels
+bool pswarpPixelsLit(const pmReadout *readout, ///< Readout to inspect
+                     psMetadata *stats, ///< Statistics to update with the result
+                     const pmConfig *config ///< Configuration
+    );
+
+bool pswarpMaskStats(const pmReadout *readout,
+		     psMetadata *stats,
+		     const pmConfig *config);
+
+
+/**
+ * define threads for this program
+ */
+bool pswarpSetThreads (void);
+
+/// Return software version
+psString pswarpVersion(void);
+
+/// Return software souce
+psString pswarpSource(void);
+
+/// Return long software version information
+psString pswarpVersionLong(void);
+
+/// Populate header with version information
+bool pswarpVersionHeader(
+    psMetadata *header                  ///< Header to populate
+    );
+
+/// Print version information
+void pswarpVersionPrint(void);
+
+/// Activate a list of files
+///
+/// File list must be NULL-terminated
+void pswarpFileActivation(pmConfig *config, // Configuration
+                          char **files, // Files to turn on/off
+                          bool state   // Activation state
+    );
+
+
+// Run down the FPA hierarchy, checking files
+bool pswarpIOChecksBefore(pmConfig *config // Configuration
+    );
+
+// Run up the FPA hierarchy, checking files
+bool pswarpIOChecksAfter(pmConfig *config // Configuration
+    );
+
+pswarpStatsFile *pswarpStatsFileAlloc ();
+pswarpStatsFile *pswarpStatsFileOpen (pmConfig *config);
+bool pswarpStatsFileSave (pmConfig *config, pswarpStatsFile *statsFile);
+
+// cleanup memory and exit
+void pswarpCleanup (pmConfig *config, pswarpStatsFile *statsFile) PS_ATTR_NORETURN;
+
+// load the astrometry header info and generate the tranformations 
+bool pswarpDefineLayout (pmConfig *config);
+
+// XXX function for testing
+bool pswarpDumpOutput (pmConfig *config);
+
+// structure to describe approximate bounds for each fpa element (eg, chip)
+// P,Q are a locally linear projection
+typedef struct {
+    psVector *Pmin; 
+    psVector *Pmax;
+    psVector *Qmin;
+    psVector *Qmax;
+} pswarpBounds;
+
+bool pswarpFindOverlap (pmFPA *input, pmFPA *output, pswarpBounds *src, pswarpBounds *tgt);
+psProjection *pswarpLocalFrame (pmFPA *fpa);
+pswarpBounds *pswarpMakeBounds (pmFPA *fpa, psProjection *frame);
+bool pswarpBoundsAppend(pswarpBounds *bounds, float Pmin, float Pmax, float Qmin, float Qmax);
+pswarpBounds *pswarpBoundsAlloc();
+
+bool pswarpLoadAstrometry (pmFPAfile *target, pmFPAfile *astrom, pmConfig *config);
+
+bool pswarpTransformToTarget (pmFPA *output, pmReadout *input, pmConfig *config, bool backgroundWarp);
+bool pswarpMakePSF (pmConfig *config, pmFPAfile *output, psMetadata *stats);
+bool pswarpUpdateStatistics (pmFPA *output, psMetadata *stats, pmFPA *input, pmFPA *astrom, pmConfig *config);
+bool pswarpUpdateMetadata (pmFPA *output, pmFPA *skycell, pmFPA *input, pmFPA *astrom, pmConfig *config, bool fullImage);
+
+// XXX functions in pswarpParseCamera
+
+bool AddStringAsArray (psMetadata *md, char *string, char *name);
+
+pmFPAfile *pswarpDefineInputFile(pmConfig *config,// Configuration
+				 pmFPAfile *bind,    // File to which to bind, or NULL
+				 char *filerule,     // Name of file rule
+				 char *argname,      // Argument name
+				 pmFPAfileType fileType // Type of file
+  );
+
+bool pswarpParseSingleInput (pmConfig *config);
+bool pswarpParseMultiInput (pmConfig *config, psMetadata *fileListMD);
+
+bool pswarpModifyChipAstrom (pmConfig *config, pmFPAview *view, pmChip *chip, pmFPAfile *astrom, bool bilevelAstrometry, double xBin, double yBin);
+bool pswarpGetInputScales (double *xBin, double *yBin, pmConfig *config, pmFPAview *view, pmChip *chip);
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpArguments.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpArguments.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpArguments.c	(revision 41091)
@@ -0,0 +1,142 @@
+/** @file pswarpArguments.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.24 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-06 03:10:36 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "pswarp.h"
+
+static void usage (void) {
+    fprintf(stderr, "USAGE: pswarp [-input input.mdc] [-file image(s)] [-list imagelist] [options] (output) (skycell)\n");
+    fprintf(stderr, "  options:\n");
+    fprintf(stderr, "    [-input input.mdc] : input image information in a metadata file\n");
+    fprintf(stderr, "    [-file input.fits[,input.fits]] : input image to be warped\n");
+    fprintf(stderr, "    [-astrom astrom.cmp] : provide an alternative astrometry calibration\n");
+    fprintf(stderr, "    [-mask mask.fits] : provide a corresponding mask image\n");
+    fprintf(stderr, "    [-variance variance.fits] : provide a corresponding variance image\n");
+    psErrorStackPrint(stderr, "\n");
+    exit(PS_EXIT_CONFIG_ERROR);
+}
+
+pmConfig *pswarpArguments (int argc, char **argv) {
+
+    int N;
+
+    if (argc == 1) {
+        usage();
+    }
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSWARP_RECIPE);
+    if (!config) {
+        psError(psErrorCodeLast(), false, "Can't read configuration");
+        return NULL;
+    }
+
+    // generic arguments (version, dumpconfig)
+    PS_ARGUMENTS_GENERIC( pswarp, config, argc, argv );
+
+    // thread arguments
+    PS_ARGUMENTS_THREADS( pswarp, config, argc, argv )
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PSWARP recipe values loaded from recipe files
+    if (!pmConfigRecipeOptions(config, PSWARP_RECIPE)) {
+        psError(psErrorCodeLast(), false, "Can't do something with recipes");
+        psFree(config);
+        return NULL;
+    }
+
+    // XXX move to the single group below?
+    pmConfigFileSetsMD(config->arguments, &argc, argv, "ASTROM",   "-astrom", "-astromlist");
+
+    // turn on psphot visualization
+    if ((N = psArgumentGet(argc, argv, "-psphot-visual"))) {
+	psArgumentRemove(N, &argc, argv);
+	pmVisualSetVisual(true);
+    }
+
+    // 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,
+                         "Only process these chips", argv[N]);
+        psArgumentRemove(N, &argc, argv);
+    }
+
+    // Statistics file
+    if ((N = psArgumentGet(argc, argv, "-stats"))) {
+        psArgumentRemove(N, &argc, argv);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "STATS", PS_DATA_STRING,
+                         "Filename for statistics of output image", argv[N]);
+        psArgumentRemove(N, &argc, argv);
+    }
+
+    pswarpSetThreads();
+
+    // there are three mutually exclusive ways of providing the input
+    // 1) supply -file (filename) [-mask .. -variance ..] on the command line
+    // 2) supply -input (input.mdc) on the command line
+    // 3) load inputs from RUN config info
+
+    // below, we check first for -file then for -input.  failure to find either implies use of
+    // the configuration metadata file.
+
+    bool singleInput = pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT", "-file", "-list");
+    if (singleInput) {
+	if (psArgumentGet(argc, argv, "-input")) {
+	    psErrorStackPrint(stderr, "error in arguments : -input and -file / -list are mutually exclusive");
+	    exit(PS_EXIT_CONFIG_ERROR);
+	}	
+	pmConfigFileSetsMD (config->arguments, &argc, argv, "MASK", "-mask", "-masklist");
+	pmConfigFileSetsMD (config->arguments, &argc, argv, "VARIANCE", "-variance", "-variancelist");
+	pmConfigFileSetsMD (config->arguments, &argc, argv, "BACKGROUND", "-background", "-bkglist");
+    } else {
+	// find the input data file (an mdc file)
+	if ((N = psArgumentGet(argc, argv, "-input"))) {
+	    if (argc <= N+1) {
+		psErrorStackPrint(stderr, "Expected to see 1 more argument; saw %d", argc - 1);
+		exit(PS_EXIT_CONFIG_ERROR);
+	    }
+	    psArgumentRemove(N, &argc, argv);
+
+	    unsigned int numBad = 0;                     // Number of bad lines
+	    psMetadata *inputs = psMetadataConfigRead(NULL, &numBad, argv[N], false); // Input file info
+	    if (!inputs || numBad > 0) {
+		psErrorStackPrint(stderr, "Unable to cleanly read MDC file with inputs.");
+		exit(PS_EXIT_CONFIG_ERROR);
+	    }
+	    psMetadataAddMetadata(config->arguments, PS_LIST_TAIL, "INPUTS", 0, "Metadata with input details", inputs);
+	    psFree(inputs);
+
+	    psArgumentRemove(N, &argc, argv);
+	}
+    }
+    if (argc != 3) {
+	usage();
+    }
+    if (psErrorCodeLast() != PS_ERR_NONE) {
+	psErrorStackPrint(stderr, "error in arguments");
+	exit(PS_EXIT_CONFIG_ERROR);
+    }
+    
+    psArray *array;
+
+    // output position is fixed
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[1]);
+
+    // skycell position is fixed
+    array = psArrayAlloc(1);
+    array->data[0] = psStringCopy(argv[2]);
+    psMetadataAddPtr(config->arguments, PS_LIST_TAIL, "SKYCELL", PS_DATA_ARRAY, "", array);
+    psFree(array);
+
+    psTrace("pswarp", 1, "Done with pswarpArguments...\n");
+    return config;
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpCleanup.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpCleanup.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpCleanup.c	(revision 41091)
@@ -0,0 +1,121 @@
+/** @file pswarp.c
+ *
+ *  @brief cleanup the open files & memory then exit
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.15 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-11 23:27:58 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "pswarp.h"
+
+// Lists of file rules which we may need to save here
+static char *outputFiles[] = {
+  "PSWARP.OUTPUT",
+  "PSWARP.OUTPUT.MASK",
+  "PSWARP.OUTPUT.VARIANCE",
+  "PSWARP.OUTPUT.BKGMODEL",
+  "PSWARP.OUTPUT.SOURCES",
+  "PSPHOT.OUTPUT",
+  "PSPHOT.RESID",
+  "PSPHOT.BACKMDL",
+  "PSPHOT.BACKMDL.STDEV",
+  "PSPHOT.BACKGND",
+  "PSPHOT.BACKSUB",
+  "PSPHOT.PSF.SAVE",
+  "SOURCE.PLOT.MOMENTS",
+  "SOURCE.PLOT.PSFMODEL",
+  "SOURCE.PLOT.APRESID",
+  NULL
+};
+
+void pswarpCleanup (pmConfig *config, pswarpStatsFile *statsFile)
+{
+    psExit exitValue = pswarpExitCode(PS_EXIT_SUCCESS); // Exit code
+
+    // activate all of the relevant output files
+    pmFPAfileActivate(config->files, false, NULL);
+    for (int i = 0; outputFiles[i] != NULL; i++) {
+        pmFPAfileActivate(config->files, true, outputFiles[i]);
+    }
+
+    pmFPAfile *output = psMetadataLookupPtr(NULL, config->files, "PSWARP.OUTPUT");
+    if (!output) {
+        psError(PSWARP_ERR_CONFIG, false, "Can't find output data!\n");
+	pmFPAfileFreeSetStrict(false);
+	goto DONE;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc(0);
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+	psError(psErrorCodeLast(), false, "Unable to read files.");
+	pmFPAfileFreeSetStrict(false);
+	goto DONE;
+    }
+
+    pmChip *chip;
+    while ((chip = pmFPAviewNextChip (view, output->fpa, 1)) != NULL) {
+	psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+	if (!chip->process || !chip->file_exists) { continue; }
+	if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+	    psError(psErrorCodeLast(), false, "Unable to read files.");
+	    pmFPAfileFreeSetStrict(false);
+	    goto DONE;
+	}
+	pmCell *cell;
+	while ((cell = pmFPAviewNextCell (view, output->fpa, 1)) != NULL) {
+	    psTrace ("pswarp", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+	    if (!cell->process || !cell->file_exists) { continue; }
+	    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE) ||
+		!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) {
+		psError(psErrorCodeLast(), false, "Unable to read files.");
+		pmFPAfileFreeSetStrict(false);
+		goto DONE;
+	    }
+	}
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) {
+	    psError(psErrorCodeLast(), false, "Unable to write files.");
+	    pmFPAfileFreeSetStrict(false);
+	    goto DONE;
+	}
+    }
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) {
+	psError(psErrorCodeLast(), false, "Unable to write files.");
+	pmFPAfileFreeSetStrict(false);
+	goto DONE;
+    }
+    psFree(view);
+
+    if (!pswarpStatsFileSave (config, statsFile)) {
+	psError(psErrorCodeLast(), false, "Unable to save stats file.");
+	exitValue = pswarpExitCode(exitValue);
+    }
+
+    // Dump configuration
+    bool mdok = false;
+    psString dump_file = psMetadataLookupStr(&mdok, config->arguments, "DUMP_CONFIG");
+    if (dump_file) {
+	if (!pmConfigDump(config, dump_file)) {
+	    psError(psErrorCodeLast(), false, "Unable to dump configuration");
+	    exitValue = pswarpExitCode(exitValue);
+	}
+    }
+
+    psThreadPoolFinalize();
+    psMemCheckCorruption(stderr, true);
+
+    psFree(config);
+
+    psTimerStop();
+    pmVisualClose();
+    pmModelClassCleanup();
+    pmConceptsDone();
+    pmConfigDone();
+    psLibFinalize();
+
+DONE:
+    exitValue = pswarpExitCode(exitValue);
+    exit (exitValue);
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpDefine.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpDefine.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpDefine.c	(revision 41091)
@@ -0,0 +1,168 @@
+/** @file pswarpDefine.c
+ *
+ *  @brief generates the output images
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.15 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-05 20:44:04 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "pswarp.h"
+
+bool pswarpDefine (pmConfig *config) {
+
+    // load the PSWARP recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSWARP_RECIPE);
+    if (!recipe) {
+        psError(PSWARP_ERR_CONFIG, true, "Can't find PSWARP recipe!\n");
+        return false;
+    }
+
+    // select the input data sources
+    pmFPAfile *skycell = psMetadataLookupPtr (NULL, config->files, "PSWARP.SKYCELL");
+    if (!skycell) {
+        psError(PSWARP_ERR_CONFIG, false, "Can't find skycell data!\n");
+        return false;
+    }
+    pmFPAfile *output = psMetadataLookupPtr(NULL, config->files, "PSWARP.OUTPUT");
+    if (!output) {
+        psError(PSWARP_ERR_CONFIG, false, "Can't find output data!\n");
+        return false;
+    }
+    pmFPAfile *input = psMetadataLookupPtr(NULL, config->files, "PSWARP.INPUT");
+    if (!input) {
+        psError(PSWARP_ERR_CONFIG, false, "Can't find input data!\n");
+        return false;
+    }
+    
+    // Read the output astrometry
+    // XXX this section loops over the input skycell to load the header info
+    {
+        pmFPAview *view = pmFPAviewAlloc(0);
+
+        pmChip *chip;
+	if (!pmFPAfileRead (skycell, view, config)) {
+	    psError(PS_ERR_IO, false, "failed READ at FPA %s", skycell->name);
+	    psFree(view);
+	    return false;
+	}
+        while ((chip = pmFPAviewNextChip (view, skycell->fpa, 1)) != NULL) {
+            psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+            if (!chip->process || !chip->file_exists) { continue; }
+	    if (!pmFPAfileRead (skycell, view, config)) {
+                psError(psErrorCodeLast(), false, "failed READ at CHIP %s", skycell->name);
+		psFree(view);
+		return false;
+            }
+            pmCell *cell;
+            while ((cell = pmFPAviewNextCell (view, skycell->fpa, 1)) != NULL) {
+                psTrace ("pswarp", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+                if (!cell->process || !cell->file_exists) { continue; }
+		if (!pmFPAfileRead (skycell, view, config)) {
+		    psError(psErrorCodeLast(), false, "failed READ at CELL %s", skycell->name);
+		    psFree(view);
+		    return false;
+                }
+		if (!pmFPAfileClose(skycell, view)) {
+		    psError(psErrorCodeLast(), false, "failed CLOSE at CELL %s", skycell->name);
+		    psFree (view);
+		    return false;
+		}
+
+		// we've got the output astrom header
+		pmHDU *hdu = pmHDUFromCell(cell); ///< HDU for source
+		if (!hdu || !hdu->header) {
+		    psError(PM_ERR_PROG, false, "Unable to find header for sky cell.");
+		    psFree(view);
+		    return false;
+		}
+		int numCols = psMetadataLookupS32(NULL, hdu->header, "NAXIS1"); ///< Number of columns
+		int numRows = psMetadataLookupS32(NULL, hdu->header, "NAXIS2"); ///< Number of rows
+		if ((numCols == 0) || (numRows == 0)) {
+		    psError(PSWARP_ERR_DATA, false, "skycell has invalid dimensions %d x %d", numCols, numRows);
+		    psFree(view);
+		    return false;
+		}
+		
+		// XXX generate the output pixels
+		pmCell *target = pmFPAviewThisCell(view, output->fpa); ///< Target cell
+		pmReadout *readout = pmReadoutAlloc(target); ///< Target readout
+		readout->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+		psImageInit(readout->image, NAN);
+		psFree(readout);                // Drop reference
+		
+		bool status = false;
+		psMetadataItemSupplement(&status, target->concepts, cell->concepts, "CELL.XBIN");
+		psMetadataItemSupplement(&status, target->concepts, cell->concepts, "CELL.YBIN");
+		psMetadataItemSupplement(&status, target->concepts, cell->concepts, "CELL.XSIZE");
+		psMetadataItemSupplement(&status, target->concepts, cell->concepts, "CELL.YSIZE");
+		psMetadataItemSupplement(&status, target->concepts, cell->concepts, "CELL.XPARITY");
+		psMetadataItemSupplement(&status, target->concepts, cell->concepts, "CELL.YPARITY");
+		psMetadataItemSupplement(&status, target->concepts, cell->concepts, "CELL.X0");
+		psMetadataItemSupplement(&status, target->concepts, cell->concepts, "CELL.Y0");
+            }
+	    if (!pmFPAfileClose(skycell, view)) {
+		psError(psErrorCodeLast(), false, "failed CLOSE at CHIP %s", skycell->name);
+		psFree (view);
+		return false;
+	    }
+	}
+	if (!pmFPAfileClose(skycell, view)) {
+	    psError(psErrorCodeLast(), false, "failed CLOSE at FPA %s", skycell->name);
+	    psFree (view);
+	    return false;
+	}
+	psFree(view);
+    }
+
+    // the section below converts the header astrometry info (in skycell) to the pmFPA
+    // astrometry structures, saving them on the output fpa structure.
+
+    // XXX this block should be modified to loop over all chips
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    view->chip = 0;
+    view->cell = 0;
+    view->readout = -1;
+    pmHDU *phu = pmFPAviewThisPHU(view, skycell->fpa); ///< Skycell PHU
+    pmHDU *hdu = pmFPAviewThisHDU(view, skycell->fpa); ///< Skycell header
+    bool bilevelAstrometry = false;
+    if (phu) {
+        char *ctype = psMetadataLookupStr(NULL, phu->header, "CTYPE1");
+        if (ctype) {
+            bilevelAstrometry = !strcmp(&ctype[4], "-DIS");
+        }
+    }
+
+    // We read from the skycell into the output.  i.e., the output receives the desired astrometry.
+    pmChip *outputChip = pmFPAviewThisChip(view, output->fpa); ///< Chip in the output
+    if (bilevelAstrometry) {
+        if (!pmAstromReadBilevelMosaic(output->fpa, phu->header)) {
+            psError(psErrorCodeLast(), false, "Unable to read bilevel mosaic astrometry for skycell.");
+            psFree(view);
+            return false;
+        }
+        if (!pmAstromReadBilevelChip(outputChip, hdu->header)) {
+            psError(psErrorCodeLast(), false, "Unable to read bilevel chip astrometry for skycell.");
+            psFree(view);
+            return false;
+        }
+    } else {
+        // we use a default FPA pixel scale of 1.0
+        if (!pmAstromReadWCS(output->fpa, outputChip, hdu->header, 1.0)) {
+            psError(psErrorCodeLast(), false, "Unable to read WCS astrometry for skycell.");
+            psFree(view);
+            return false;
+        }
+    }
+    
+    view->chip = view->cell = view->readout = -1;
+    pmFPAAddSourceFromView(output->fpa, view, output->format);
+
+
+    psFree (view);
+    return true;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpDefineBackground.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpDefineBackground.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpDefineBackground.c	(revision 41091)
@@ -0,0 +1,155 @@
+/** @file pswarpDefineBackground.c
+ * 
+ *  @brief generates the output background model
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.15 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-05 20:44:04 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "pswarp.h"
+
+bool pswarpDefineBackground (pmConfig *config) {
+
+    if (!psMetadataLookupBool(NULL,config->arguments,"BACKGROUND.MODEL")) return true;
+
+    // load the PSWARP recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSWARP_RECIPE);
+    if (!recipe) {
+        psError(PSWARP_ERR_CONFIG, true, "Can't find PSWARP recipe!\n");
+        return false;
+    }
+
+    // select the input data sources
+    pmFPAfile *skycell = psMetadataLookupPtr (NULL, config->files, "PSWARP.SKYCELL");
+    if (!skycell) {
+        psError(PSWARP_ERR_CONFIG, false, "Can't find skycell data!\n");
+        return false;
+    }
+    pmFPAfile *output = psMetadataLookupPtr(NULL, config->files, "PSWARP.OUTPUT.BKGMODEL");
+    if (!output) {
+        psError(PSWARP_ERR_CONFIG, false, "Can't find output data!\n");
+        return false;
+    }
+    pmFPAfile *input = psMetadataLookupPtr(NULL, config->files, "PSWARP.INPUT");
+    if (!input) {
+        psError(PSWARP_ERR_CONFIG, false, "Can't find input data!\n");
+        return false;
+    }
+    
+    // open the full skycell file; no need to defer different depths. only load the header data
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmFPAfileOpen (skycell, view, config);
+
+    // Read header and create target
+    {
+        if (!pmFPAReadHeaderSet(skycell->fpa, skycell->fits, config)) {
+            psError(psErrorCodeLast(), false, "Unable to read headers for skycell.");
+            psFree(view);
+            return false;
+        }
+        view->chip = 0;
+        view->cell = 0;
+        view->readout = 0;
+        pmCell *source = pmFPAfileThisCell(config->files, view, "PSWARP.SKYCELL"); ///< Source cell
+        pmHDU *hdu = pmHDUFromCell(source); ///< HDU for source
+        if (!hdu || !hdu->header) {
+            psError(PM_ERR_PROG, false, "Unable to find header for sky cell.");
+            psFree(view);
+            return false;
+        }
+        int numCols = psMetadataLookupS32(NULL, hdu->header, "NAXIS1"); ///< Number of columns
+        int numRows = psMetadataLookupS32(NULL, hdu->header, "NAXIS2"); ///< Number of rows
+        if ((numCols == 0) || (numRows == 0)) {
+            psError(PSWARP_ERR_DATA, false, "skycell has invalid dimensions %d x %d", numCols, numRows);
+            psFree(view);
+            return false;
+        }
+
+        pmCell *target = pmFPAviewThisCell(view, output->fpa); ///< Target cell
+        pmReadout *readout = pmReadoutAlloc(target); ///< Target readout
+        readout->image = psImageAlloc(numCols / output->xBin, numRows / output->yBin, PS_TYPE_F32);
+        psImageInit(readout->image, NAN);
+        psFree(readout);                // Drop reference
+
+        bool status = false;
+        psMetadataItemSupplement(&status, target->concepts, source->concepts, "CELL.XBIN");
+        psMetadataItemSupplement(&status, target->concepts, source->concepts, "CELL.YBIN");
+ 	// psMetadataAddS32(target->concepts,PS_LIST_TAIL,"CELL.XBIN",PS_META_REPLACE,"",output->xBin);
+ 	// psMetadataAddS32(target->concepts,PS_LIST_TAIL,"CELL.YBIN",PS_META_REPLACE,"",output->yBin);
+        psMetadataItemSupplement(&status, target->concepts, source->concepts, "CELL.XSIZE");
+        psMetadataItemSupplement(&status, target->concepts, source->concepts, "CELL.YSIZE");
+ 	// psMetadataAddS32(target->concepts,PS_LIST_TAIL,"CELL.XSIZE",PS_META_REPLACE,"",numCols / output->xBin); 
+ 	// psMetadataAddS32(target->concepts,PS_LIST_TAIL,"CELL.YSIZE",PS_META_REPLACE,"",numRows / output->yBin);
+        psMetadataItemSupplement(&status, target->concepts, source->concepts, "CELL.XPARITY");
+        psMetadataItemSupplement(&status, target->concepts, source->concepts, "CELL.YPARITY");
+        psMetadataItemSupplement(&status, target->concepts, source->concepts, "CELL.X0");
+        psMetadataItemSupplement(&status, target->concepts, source->concepts, "CELL.Y0");
+    }
+
+    // XXX this is not a sufficient test
+    view->chip = 0;
+    view->cell = 0;
+    view->readout = -1;
+    pmHDU *phu = pmFPAviewThisPHU(view, skycell->fpa); ///< Skycell PHU
+    pmHDU *hdu = pmFPAviewThisHDU(view, skycell->fpa); ///< Skycell header
+
+    pmAstromWCS *WCS = pmAstromWCSfromHeader(hdu->header);
+
+    double cd1f = 1.0 * output->xBin;
+    double cd2f = 1.0 * output->yBin;
+    
+    WCS->crpix1 = WCS->crpix1 / cd1f;
+    WCS->crpix2 = WCS->crpix2 / cd2f;
+    
+    WCS->cdelt1 *= cd1f;
+    WCS->cdelt2 *= cd2f;
+
+    WCS->trans->x->coeff[1][0] *= cd1f;
+    WCS->trans->x->coeff[0][1] *= cd2f;
+    WCS->trans->y->coeff[1][0] *= cd1f;
+    WCS->trans->y->coeff[0][1] *= cd2f;
+    
+    
+    pmAstromWCStoHeader (hdu->header,WCS);
+
+    bool bilevelAstrometry = false;
+    if (phu) {
+        char *ctype = psMetadataLookupStr(NULL, phu->header, "CTYPE1");
+        if (ctype) {
+            bilevelAstrometry = !strcmp(&ctype[4], "-DIS");
+        }
+    }
+
+    // We read from the skycell into the output.  i.e., the output receives the desired astrometry.
+    pmChip *outputChip = pmFPAviewThisChip(view, output->fpa); ///< Chip in the output
+    if (bilevelAstrometry) {
+        if (!pmAstromReadBilevelMosaic(output->fpa, phu->header)) {
+            psError(psErrorCodeLast(), false, "Unable to read bilevel mosaic astrometry for skycell.");
+            psFree(view);
+            return false;
+        }
+        if (!pmAstromReadBilevelChip(outputChip, hdu->header)) {
+            psError(psErrorCodeLast(), false, "Unable to read bilevel chip astrometry for skycell.");
+            psFree(view);
+            return false;
+        }
+    } else {
+        // we use a default FPA pixel scale of 1.0
+      if (!pmAstromReadWCS(output->fpa, outputChip, hdu->header, 1.0)) {
+            psError(psErrorCodeLast(), false, "Unable to read WCS astrometry for skycell.");
+            psFree(view);
+            return false;
+        }
+    }
+
+    
+    view->chip = view->cell = view->readout = -1;
+    pmFPAAddSourceFromView(output->fpa, view, output->format);
+
+
+    psFree (view);
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpDefineLayout.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpDefineLayout.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpDefineLayout.c	(revision 41091)
@@ -0,0 +1,199 @@
+/** @file pswarpDefineLayout.c
+ *
+ *  @brief load input & output astrometry, determine overlaps
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @date $Date: 2009-02-05 20:44:04 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "pswarp.h"
+
+bool pswarpGenerateOutputCell (pmChip *chip, pmCell *cell, pmCell *refcell, float xBin, float yBin);
+
+// load the astrometry header info and generate the tranformations 
+bool pswarpDefineLayout (pmConfig *config) {
+
+    bool status = false;
+
+    pmFPAfile *output = psMetadataLookupPtr(&status, config->files, "PSWARP.OUTPUT");
+    if (!output) {
+        psError(PSWARP_ERR_CONFIG, false, "Can't find output data!\n");
+        return false;
+    }
+    // select the input data sources
+    pmFPAfile *skycell = psMetadataLookupPtr (&status, config->files, "PSWARP.SKYCELL");
+    if (!skycell) {
+        psError(PSWARP_ERR_CONFIG, false, "Can't find skycell data!\n");
+        return false;
+    }
+    if (!pswarpLoadAstrometry (output, skycell, config)) {
+        psError(PSWARP_ERR_CONFIG, false, "problem loading output astrometry\n");
+        return false;
+    }
+
+    // if a background model is supplied and the output is requested, then 
+    // this will exist.  if not, skip related features 
+    pmFPAfile *bkgModel = psMetadataLookupPtr(&status, config->files, "PSWARP.OUTPUT.BKGMODEL");
+
+    // chips are not processed unless we have determined they overlap the inputs
+    pmFPAExcludeChips (output->fpa);
+    if (bkgModel) {
+        pmFPAExcludeChips (bkgModel->fpa);
+    }
+
+    int nInputs = psMetadataLookupS32(&status, config->arguments, "NUM_INPUTS");
+    if (!status) {
+        psError(PSWARP_ERR_DATA, true, "number of inputs is not defined (programming error)");
+        return false;
+    }
+
+    for (int i = 0; i < nInputs; i++) {
+	// place input astrometry transformations in 'input'
+	pmFPAfile *input = pmFPAfileSelectSingle(config->files, "PSWARP.INPUT", i);
+	if (!input) {
+	    psError(PSWARP_ERR_CONFIG, false, "Can't find input data!\n");
+	    return false;
+	}
+
+	// input astrometry may be embedded in 'input' or supplied separately
+	pmFPAfile *astrom = pmFPAfileSelectSingle(config->files, "PSWARP.ASTROM", i);
+	if (!astrom) {
+	    astrom = input;
+	}
+	if (astrom->camera != input->camera) {
+	    // XXX this may cause an error for output / skycell
+	    psError(PSWARP_ERR_DATA, true, "Input camera and astrometry camera do not match.");
+	    return false;
+	}
+	if (!pswarpLoadAstrometry (input, astrom, config)) {
+	    psError(PSWARP_ERR_CONFIG, false, "problem loading input astrometry\n");
+	    return false;
+	}
+
+	// given the input data, determine which output elements should be generated
+	// and generate the appropriate images
+
+	// find the R,D center for the skycell, use for common projection
+	psProjection *frame = pswarpLocalFrame (skycell->fpa);
+
+	// generate Lmin,max, Mmin,max for both datasets
+	pswarpBounds *srcBounds = pswarpMakeBounds (input->fpa, frame);
+	pswarpBounds *tgtBounds = pswarpMakeBounds (skycell->fpa, frame);
+
+	// find the output (tgt) chips which overlap the input (src) chips
+	pswarpFindOverlap (input->fpa, output->fpa, srcBounds, tgtBounds);
+	psFree (srcBounds);
+	psFree (tgtBounds);
+    }
+
+    // The loop below generates the output pixels. XXX Should this be deferred until we
+    // actually need them?  
+
+    // Generate the output chips (pixels on output->fpa, concepts from skycell->fpa)
+    pmFPAview *view = pmFPAviewAlloc(0);
+
+    // check input astrometry mode
+    bool bilevelAstrometry = psMetadataLookupBool (NULL, skycell->fpa->analysis, "ASTROMETRY.BILEVEL");
+    
+    // XXX this is a bit muddled : we are setting up the astrometry elements for the output
+    // background model here instead of in pswarpLoadAstrometry, where it would be more natural
+    // the only explanation I have is that 1) I need to have the binning factors for the input image, but 
+    // 2) at this point I don't yet have the input background models loaded and 3) the source
+    // for the binning factor of the input and output background models is different
+    // (header->CCDSUMi for input, recipe / pmFPAfile.xbin.ybin for the other)
+
+    // if we have an output background model, generate hdu, etc and supply astrometry
+    if (bkgModel && bilevelAstrometry) {
+	// top-level elements of the bkgModel astrometry
+	bkgModel->fpa->toTPA   = psMemIncrRefCounter (skycell->fpa->toTPA);
+	bkgModel->fpa->fromTPA = psMemIncrRefCounter (skycell->fpa->fromTPA);
+	bkgModel->fpa->toSky   = psMemIncrRefCounter (skycell->fpa->toSky);
+    }
+
+    pmChip *chip;
+    while ((chip = pmFPAviewNextChip (view, output->fpa, 1)) != NULL) {
+	psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+	if (!chip->process) { continue; }
+	pmCell *cell;
+	while ((cell = pmFPAviewNextCell (view, output->fpa, 1)) != NULL) {
+	    psTrace ("pswarp", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+	    if (!cell->process) { continue; }
+
+	    pmCell *refcell = pmFPAviewThisCell(view, skycell->fpa); ///< Target cell
+
+	    if (!pswarpGenerateOutputCell (chip, cell, refcell, output->xBin, output->yBin)){
+		psError(PSWARP_ERR_DATA, false, "failed to generate output cell");
+		psFree(view);
+		return false;
+	    }
+
+	    if (bkgModel) {
+		pmChip *bkgChip = pmFPAviewThisChip(view, bkgModel->fpa); ///< Target cell
+		pmCell *bkgCell = pmFPAviewThisCell(view, bkgModel->fpa); ///< Target cell
+		if (!pswarpGenerateOutputCell (bkgChip, bkgCell, refcell, bkgModel->xBin, bkgModel->yBin)) {
+		    psError(PSWARP_ERR_DATA, false, "failed to generate output cell");
+		    psFree(view);
+		    return false;
+		}
+		if (!pswarpModifyChipAstrom (config, view, bkgChip, skycell, bilevelAstrometry, bkgModel->xBin, bkgModel->yBin)) {
+		    psError(PSWARP_ERR_DATA, false, "problem with output astrometry");
+		    psFree(view);
+		    return false;
+		}
+	    }
+	}
+    }
+    psFree (view);
+    return true;
+}
+
+bool pswarpGenerateOutputCell (pmChip *chip, pmCell *cell, pmCell *refcell, float xBin, float yBin) {
+
+    bool status = false;
+
+    // we've got the output astrom header
+    pmHDU *hdu = pmHDUFromCell(refcell); ///< HDU for source
+    if (!hdu || !hdu->header) {
+	psError(PM_ERR_PROG, false, "Unable to find header for sky cell.");
+	return false;
+    }
+    int numCols = psMetadataLookupS32(&status, hdu->header, "NAXIS1") / xBin; ///< Number of output columns
+    int numRows = psMetadataLookupS32(&status, hdu->header, "NAXIS2") / yBin; ///< Number of output rows
+    if ((numCols == 0) || (numRows == 0)) {
+	psError(PSWARP_ERR_DATA, false, "output cell has invalid dimensions %d x %d", numCols, numRows);
+	return false;
+    }
+		
+    // generate the pixels for output->fpa
+    pmReadout *readout = pmReadoutAlloc(cell); ///< output readout
+    readout->image = psImageAlloc(numCols, numRows, PS_TYPE_F32);
+
+    psImageInit(readout->image, NAN);
+    psFree(readout);                // Drop reference
+		
+    // copy the image concepts from the skycell 
+    psMetadataItemSupplement(&status, cell->concepts, refcell->concepts, "CELL.XBIN");
+    psMetadataItemSupplement(&status, cell->concepts, refcell->concepts, "CELL.YBIN");
+    psMetadataItemSupplement(&status, cell->concepts, refcell->concepts, "CELL.XSIZE");
+    psMetadataItemSupplement(&status, cell->concepts, refcell->concepts, "CELL.YSIZE");
+    psMetadataItemSupplement(&status, cell->concepts, refcell->concepts, "CELL.XPARITY");
+    psMetadataItemSupplement(&status, cell->concepts, refcell->concepts, "CELL.YPARITY");
+    psMetadataItemSupplement(&status, cell->concepts, refcell->concepts, "CELL.X0");
+    psMetadataItemSupplement(&status, cell->concepts, refcell->concepts, "CELL.Y0");
+
+    chip->data_exists = true;
+    chip->file_exists = true;
+    chip->process     = true;
+
+    cell->data_exists = true;
+    cell->file_exists = true;
+    cell->process     = true;
+
+    // copy the basic headers across from astrom ref to output
+    pmHDU *outHDU = pmHDUFromCell (cell);           ///< HDU for the output warped image
+    outHDU->header = psMetadataCopy(outHDU->header, hdu->header);
+    pswarpVersionHeader(outHDU->header);
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpDefineSkycell.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpDefineSkycell.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpDefineSkycell.c	(revision 41091)
@@ -0,0 +1,165 @@
+/** @file pswarpDefineSkycell.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.12 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-05 20:44:04 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "pswarp.h"
+
+/**
+ * \brief XXX this function is based on pmFPAfileDefineFromArgs
+ * a skycell consists of only one file
+
+ * search for argname on the config->argument list and construct
+ * an FPA based on the files in this list (must represent a single FPA)
+ * built the association between the FPA elements (CHIP/CELL) and the files
+ * define the pmFPAfile filename and bind it to this FPA
+ * save the pmFPAfile on config->files
+ * @return the pmFPAfile (a view to the one saved on config->files)
+ */
+bool pswarpDefineSkycell (pmFPAfile **outFile, pmConfig **outConfig, pmConfig *config, const char *filename, const char *argname)
+{
+    PS_ASSERT_PTR_NON_NULL(config, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(filename, NULL);
+    PS_ASSERT_STRING_NON_EMPTY(argname, NULL);
+
+    bool status;
+    pmFPA *fpa = NULL;
+    psFits *fits = NULL;
+    pmFPAfile *file = NULL;
+    psMetadata *phu = NULL;
+    psMetadata *format = NULL;
+    pmConfig *skyConfig = NULL;
+
+    *outFile = NULL;
+    *outConfig = NULL;
+
+    // we search the argument data for the named fileset (argname)
+    psArray *infiles = psMetadataLookupPtr(&status, config->arguments, argname);
+    if (!status) {
+        return false;
+    }
+    if (infiles->n != 1) {
+        psError(PSWARP_ERR_CONFIG, false, "Found n == %ld files in %s in arguments\n", infiles->n, argname);
+        return false;
+    }
+
+    // this function is implicitly an INPUT operation: do not create the file
+    psString realName = pmConfigConvertFilename (infiles->data[0], config, false, false);
+    if (!realName) {
+        psError(psErrorCodeLast(), false, "Failed to convert file name %s\n", (char *) infiles->data[0]);
+        return false;
+    }
+
+    // load the header of the image
+    fits = psFitsOpen (realName, "r");
+    if (!fits) {
+        psError(psErrorCodeLast(), false, "Failed to open file %s\n", realName);
+        psFree (realName);
+        return false;
+    }
+    phu = psFitsReadHeader (NULL, fits);
+    if (!phu) {
+        psError(psErrorCodeLast(), false, "Failed to read file header %s\n", realName);
+        psFree (realName);
+        return false;
+    }
+    psFitsClose(fits);
+
+    { // this bit is unique to pswarpDefineSkycell:
+
+      // We need to force the format for the skycell to be equivalent to SIMPLE.  Determine
+      // the current format from the header; Determine camera if not specified already
+      // XXX EAM : this operation should be defined as a pmConfig function (pmConfigCopy?)
+      skyConfig = pmConfigAlloc();
+      skyConfig->user = psMemIncrRefCounter(config->user);
+      skyConfig->system = psMemIncrRefCounter(config->system);
+
+      psFree (skyConfig->files);
+      skyConfig->files = psMemIncrRefCounter(config->files);
+      psFree (skyConfig->arguments);
+      skyConfig->arguments = psMemIncrRefCounter(config->arguments);
+    }
+
+    format = pmConfigCameraFormatFromHeader (NULL, NULL, NULL, skyConfig, phu, false);
+    if (!format) {
+        psError(psErrorCodeLast(), false, "Failed to read CCD format from %s\n", realName);
+        psFree(phu);
+        psFree (realName);
+        return false;
+    }
+
+    // Record the camera name of the skycell, so we can save its configuration
+    // XXX this can be put outside of the f
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "SKYCELL.CAMERA", 0, "Name of camera for skycell", skyConfig->cameraName);
+
+    // build the template fpa, set up the basic view
+    fpa = pmFPAConstruct (skyConfig->camera, skyConfig->cameraName);
+    if (!fpa) {
+        psError(psErrorCodeLast(), false, "Failed to construct FPA from %s", realName);
+        psFree (realName);
+        return false;
+    }
+    psFree (realName);
+
+    // load the given filerule (from config->camera) and bind it to the fpa
+    // the returned file is just a view to the entry on config->files
+    file = pmFPAfileDefineInput (skyConfig, fpa, NULL, filename);
+    if (!file) {
+        psError(psErrorCodeLast(), false, "file %s not defined\n", filename);
+        psFree(phu);
+        psFree(fpa);
+        psFree(format);
+        return false;
+    }
+    psFree (format);
+    file->format = psMemIncrRefCounter(format);
+
+    // adjust the rules to identify these files in the file->names data
+    psFree (file->filerule);
+    file->filerule = psStringCopy ("@FILES");
+    file->filesrc = psStringCopy ("{CHIP.NAME}.{CELL.NAME}");
+
+    file->fileLevel = pmFPAPHULevel(format);
+    if (file->fileLevel == PM_FPA_LEVEL_NONE) {
+        psError(PSWARP_ERR_CONFIG, true, "Unable to determine file level for %s\n", file->name);
+        psFree(phu);
+        psFree(fpa);
+        psFree(format);
+        psFree(file);
+        return false;
+    }
+
+    // set the view to the corresponding entry for this phu
+    pmFPAview *view = pmFPAAddSourceFromHeader (fpa, phu, format);
+    if (!view) {
+        psError(psErrorCodeLast(), false, "Unable to determine source for %s", file->name);
+        psFree(phu);
+        psFree (fpa);
+        psFree (format);
+        return false;
+    }
+
+    // associate the filename with the FPA element
+    char *name = pmFPAfileNameFromRule (file->filesrc, file, view);
+
+    // save the name association in the pmFPAfile structure
+    psMetadataAddStr (file->names, PS_LIST_TAIL, name, 0, "", infiles->data[0]);
+
+    psFree (view);
+    psFree (name);
+    psFree (phu);
+    psFree (fpa);
+
+    *outFile = file;
+    *outConfig = skyConfig;
+
+    return true;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpErrorCodes.c.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpErrorCodes.c.in	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpErrorCodes.c.in	(revision 41091)
@@ -0,0 +1,37 @@
+/** @file pswarpErrorCodes.c.in
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-05 20:44:04 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "pslib.h"
+#include "pswarpErrorCodes.h"
+
+/*
+ * The line
+    { PSWARP_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+void pswarpErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PSWARP_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PSWARP_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PSWARP_ERR_NERROR - PSWARP_ERR_BASE; ///< number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpErrorCodes.dat
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpErrorCodes.dat	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpErrorCodes.dat	(revision 41091)
@@ -0,0 +1,11 @@
+#
+# This file is used to generate pswarpErrorClasses.h
+#
+BASE = 8000		First value we use; lower values belong to psLib
+UNKNOWN			Unknown PSWARP error code
+NOT_IMPLEMENTED		Desired feature is not yet implemented
+ARGUMENTS		Incorrect arguments
+CONFIG			Problem in configure files
+IO			Problem in FITS I/O
+DATA                    Problem in data values
+NO_OVERLAP		No overlap between input and skycell
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpErrorCodes.h.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpErrorCodes.h.in	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpErrorCodes.h.in	(revision 41091)
@@ -0,0 +1,30 @@
+/** @file pswarpErrorCodes.h.in
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-05 20:44:04 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#if !defined(PSWARP_ERROR_CODES_H)
+#define PSWARP_ERROR_CODES_H
+/*
+ * The line
+ *  PSWARP_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PSWARP_ERR_BASE = 8000,
+    PSWARP_ERR_${ErrorCode},
+    PSWARP_ERR_NERROR
+} pswarpErrorCode;
+
+void pswarpErrorRegister(void);
+
+#endif
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpExitCode.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpExitCode.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpExitCode.c	(revision 41091)
@@ -0,0 +1,76 @@
+/** @file pswarpExit.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.8 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-05 20:44:04 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "pswarp.h"
+
+psExit pswarpExitCode(psExit exitValue)
+{
+    if (exitValue != PS_EXIT_SUCCESS) {
+        return exitValue;
+    }
+
+    // gcc -Wswitch complains here if err is declared as type psErrorCode
+    // the collection of ps*ErrorCode values are enums defined separately for 
+    // each module (psphot, pswarp, etc).  the lowest type, psErrorCode is only the base set and does
+    // not include the possible psphot values
+
+    // for now, to get around this, we just use an int for the switch
+
+    // psErrorCode errorCode = psErrorCodeLast(); // Error code
+    int errorCode = psErrorCodeLast(); // Error code
+
+    if (errorCode != PS_ERR_NONE) {
+        pmFPAfileFreeSetStrict(false);
+        psErrorStackPrint(stderr, "Unable to perform warp.");
+        switch (errorCode) {
+          case PSWARP_ERR_UNKNOWN:
+          case PS_ERR_UNKNOWN:
+            psLogMsg("pswarp", PS_LOG_WARN, "Unknown error code: %x", errorCode);
+            return PS_EXIT_UNKNOWN_ERROR;
+          case PS_ERR_IO:
+          case PS_ERR_DB_CLIENT:
+          case PS_ERR_DB_SERVER:
+          case PS_ERR_BAD_FITS:
+          case PS_ERR_OS_CALL_FAILED:
+          case PM_ERR_SYS:
+          case PSWARP_ERR_IO:
+            psLogMsg("pswarp", PS_LOG_WARN, "I/O error code: %x", errorCode);
+            return PS_EXIT_SYS_ERROR;
+          case PS_ERR_BAD_PARAMETER_VALUE:
+          case PS_ERR_BAD_PARAMETER_TYPE:
+          case PS_ERR_BAD_PARAMETER_NULL:
+          case PS_ERR_BAD_PARAMETER_SIZE:
+          case PSWARP_ERR_ARGUMENTS:
+          case PSWARP_ERR_CONFIG:
+            psLogMsg("pswarp", PS_LOG_WARN, "Configuration error code: %x", errorCode);
+            return PS_EXIT_CONFIG_ERROR;
+          case PSPHOT_ERR_PSF:
+          case PSWARP_ERR_DATA:
+          case PSWARP_ERR_NO_OVERLAP:
+            psLogMsg("pswarp", PS_LOG_WARN, "Data error code: %x", errorCode);
+            return PS_EXIT_DATA_ERROR;
+          case PS_ERR_UNEXPECTED_NULL:
+          case PS_ERR_PROGRAMMING:
+          case PSWARP_ERR_NOT_IMPLEMENTED:
+            psLogMsg("pswarp", PS_LOG_WARN, "Programming error code: %x", errorCode);
+            exitValue = PS_EXIT_PROG_ERROR;
+            break;
+          default:
+            // It's a programming error if we're not dealing with the error correctly
+            psLogMsg("pswarp", PS_LOG_WARN, "Unrecognised error code: %x", errorCode);
+            return PS_EXIT_PROG_ERROR;
+        }
+    }
+
+    return exitValue;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpFileNames.h
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpFileNames.h	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpFileNames.h	(revision 41091)
@@ -0,0 +1,53 @@
+/** this file contains the lists of file rules used by pswarpLoop at different analysis steps
+    we use these lists to turn on or off different I/O files at different stages **/
+
+// Lists must end with a NULL
+
+// Lists of file rules for the detectors and skycells, and an independent list of everything else
+
+
+// Lists of file rules for the detectors
+static char *detectorFiles[] = {
+  "PSWARP.INPUT",
+  "PSWARP.MASK",
+  "PSWARP.VARIANCE",
+  "PSWARP.BKGMODEL",
+  NULL
+};
+
+// Lists of file rules for the skycells
+static char *skycellFiles[] = {
+  "PSWARP.OUTPUT",
+  "PSWARP.OUTPUT.MASK",
+  "PSWARP.OUTPUT.VARIANCE",
+  "PSWARP.OUTPUT.BKGMODEL",
+  NULL
+};
+
+// Lists of file rules for photometry
+static char *photFiles[] = {
+  "PSPHOT.INPUT",
+  "PSPHOT.OUTPUT",
+#if PSPHOT_FIND_PSF
+  "PSPHOT.INPUT.CMF",
+#endif
+  "PSPHOT.RESID",
+  "PSPHOT.BACKMDL",
+  "PSPHOT.BACKMDL.STDEV",
+  "PSPHOT.BACKGND",
+  "PSPHOT.BACKSUB",
+  "PSPHOT.PSF.SAVE",
+  "SOURCE.PLOT.MOMENTS",
+  "SOURCE.PLOT.PSFMODEL",
+  "SOURCE.PLOT.APRESID",
+  NULL
+};
+
+// Lists of file rules for the detectors and skycells, and an independent list of everything else
+static char *independentFiles[] = {
+  "PSWARP.ASTROM", // Read independently from the input pixels
+  "PSWARP.SKYCELL", // Don't care about the skycell once we have its WCS
+  "PSWARP.OUTPUT.SOURCES", // Save these independently so we can do the PSF
+  NULL
+};
+
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpFiles.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpFiles.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpFiles.c	(revision 41091)
@@ -0,0 +1,65 @@
+/** @file pswarpFiles.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.38 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-13 21:54:32 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "pswarp.h"
+
+void pswarpFileActivation(pmConfig *config, char **files, bool state)
+{
+    for (int i = 0; files[i] != NULL; i++) {
+        pmFPAfileActivate(config->files, state, files[i]);
+    }
+    return;
+}
+
+bool pswarpIOChecksBefore(pmConfig *config)
+{
+    pmFPAview *view = pmFPAviewAlloc(0); // View for checking
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+        psFree(view);
+        return false;
+    }
+    view->chip = 0;
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+        psFree(view);
+        return false;
+    }
+    view->cell = 0;
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+        psFree(view);
+        return false;
+    }
+    psFree(view);
+    return true;
+}
+
+bool pswarpIOChecksAfter(pmConfig *config)
+{
+    pmFPAview *view = pmFPAviewAlloc(0); // View for checking
+    view->chip = view->cell = 0;
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+        psFree(view);
+        return false;
+    }
+    view->cell = -1;
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+        psFree(view);
+        return false;
+    }
+    view->chip = -1;
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+        psFree(view);
+        return false;
+    }
+    psFree(view);
+    return true;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpHeadersLoad.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpHeadersLoad.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpHeadersLoad.c	(revision 41091)
@@ -0,0 +1,130 @@
+/** @file pswarpHeadersLoad.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-05 20:44:04 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "pswarp.h"
+
+/** XXX this function should load all of the PSWARP.INPUT headers
+ * it should examine the overlap between each chip in PSWARP.INPUT
+ * and the output (pswarpMatchRange) and select/de-select the chips
+ * and/or cell which contribute pixels.
+ *
+ * pswarpDataLoad should then load the pixel of the needed chips
+ *
+ * all of the different astrometry analysis modes use the same data load loop
+ */
+bool pswarpHeadersLoad (pmConfig *config) {
+
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+    pmFPAview *view;
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSWARP.INPUT");
+    if (!input) {
+        psError(PSWARP_ERR_CONFIG, true, "Can't find input data!\n");
+        return false;
+    }
+
+    // use the external astrometry source if supplied
+    pmFPAfile *astrom = psMetadataLookupPtr (NULL, config->files, "PSWARP.ASTROM");
+    if (!astrom) {
+        astrom = input;
+    }
+
+    // select the output readout
+    view = pmFPAviewAlloc (0);
+    view->chip = 0;
+    view->cell = 0;
+    view->readout = 0;
+    pmReadout  *output = pmFPAfileThisReadout (config->files, view, "PSWARP.OUTPUT");
+    if (!output) {
+        psError(PSWARP_ERR_CONFIG, true, "Can't find output data!\n");
+        return false;
+    }
+    psFree (view);
+
+    // de-activate PSWARP.SKYCELL and PSWARP.OUTPUT
+    pmFPAfileActivate(config->files, false, "PSWARP.SKYCELL");
+    pmFPAfileActivate(config->files, false, "PSWARP.OUTPUT");
+
+    view = pmFPAviewAlloc (0);
+
+    // XXX need to read only the headers for the skycell
+    // XXX these pmAstromReadBilevel functions seem to be broken
+
+    // find the FPA phu
+    bool bilevelAstrometry = false;
+    pmHDU *phu = pmFPAviewThisPHU(view, astrom->fpa);
+    if (phu) {
+        char *ctype = psMetadataLookupStr (NULL, phu->header, "CTYPE1");
+        if (ctype) {
+            bilevelAstrometry = !strcmp(&ctype[4], "-DIS");
+        }
+    }
+    if (bilevelAstrometry) {
+        if (!pmAstromReadBilevelMosaic(input->fpa, phu->header)) {
+            psError(psErrorCodeLast(), false, "Unable to read bilevel mosaic astrometry for input.");
+            psFree(view);
+            return false;
+        }
+    }
+
+    // files associated with the science image
+    pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+
+    while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+        psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+        pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+
+        // read WCS data from the corresponding header
+        pmHDU *hdu = pmFPAviewThisHDU (view, astrom->fpa);
+        if (bilevelAstrometry) {
+            if (!pmAstromReadBilevelChip (chip, hdu->header)) {
+                psError(psErrorCodeLast(), false, "Unable to read bilevel chip astrometry for input.");
+                psFree(view);
+                return false;
+            }
+        } else {
+            // we use a default FPA pixel scale of 1.0
+            if (!pmAstromReadWCS(input->fpa, chip, hdu->header, 1.0)) {
+                psError(psErrorCodeLast(), false, "Unable to read WCS astrometry for input.");
+                psFree(view);
+                return false;
+            }
+        }
+
+        while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psTrace ("pswarp", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+            pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+
+            // process each of the readouts
+            while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+                pmFPAfileIOChecks (config, view, PM_FPA_BEFORE);
+                if (! readout->data_exists) { continue; }
+
+                // XXX Replace with a function to examine the overlap and turn on/off chips
+                // based on that result
+                // pswarpTransformReadout_Opt (output, readout, config);
+
+                pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+            }
+            pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+        }
+        pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+    }
+    pmFPAfileIOChecks (config, view, PM_FPA_AFTER);
+    psFree (view);
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpLoadAstrometry.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpLoadAstrometry.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpLoadAstrometry.c	(revision 41091)
@@ -0,0 +1,167 @@
+/** @file pswarpLoadAstrometry.c
+ *
+ *  @brief load the headers and construct astrometric transformations 
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @date $Date: 2009-02-05 20:44:04 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "pswarp.h"
+
+// we have a pmFPAfile which defines the astrometry via headers.  we force it to have type
+// WCS and read the headers.  We place the resulting info on the target astrometry
+// containers
+bool pswarpLoadAstrometry (pmFPAfile *target, pmFPAfile *astrom, pmConfig *config) {
+
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+
+    // XXX assert that astrom be WCS or CMF?
+
+    // XXX set the type to be WCS
+    pmFPAfileType saveType = astrom->type;
+    astrom->type = PM_FPA_FILE_WCS;
+
+    // **** read in all of the headers from the astrometry source
+
+    pmFPAview *view = pmFPAviewAlloc(0);
+
+    if (!pmFPAfileRead (astrom, view, config)) {
+	psError(PS_ERR_IO, false, "failed READ at FPA %s", astrom->name);
+	psFree(view);
+	return false;
+    }
+
+    while ((chip = pmFPAviewNextChip (view, astrom->fpa, 1)) != NULL) {
+	psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+	if (!chip->process || !chip->file_exists) { continue; }
+        pmChip *targetChip = pmFPAviewThisChip(view, target->fpa); // < Chip in the output
+	if (!targetChip || !targetChip->process || !targetChip->file_exists) {
+            continue; // only load astrometry into output chips which exist!
+        }
+	if (!pmFPAfileRead (astrom, view, config)) {
+	    psError(psErrorCodeLast(), false, "failed READ at CHIP %s", astrom->name);
+	    psFree(view);
+	    return false;
+	}
+	while ((cell = pmFPAviewNextCell (view, astrom->fpa, 1)) != NULL) {
+	    psTrace ("pswarp", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+	    if (!cell->process) { continue; }
+	    if (!pmFPAfileRead (astrom, view, config)) {
+		psError(psErrorCodeLast(), false, "failed READ at CELL %s", astrom->name);
+		psFree(view);
+		return false;
+	    }
+	    if (!pmFPAfileClose(astrom, view)) {
+		psError(psErrorCodeLast(), false, "failed CLOSE at CELL %s", astrom->name);
+		psFree (view);
+		return false;
+	    }
+
+	    // we've got the output astrom header
+	    pmHDU *hdu = pmHDUFromCell(cell); ///< HDU for source
+	    if (!hdu || !hdu->header) {
+		psError(PM_ERR_PROG, false, "Unable to find header for sky cell.");
+		psFree(view);
+		return false;
+	    }
+	}
+	if (!pmFPAfileClose(astrom, view)) {
+	    psError(psErrorCodeLast(), false, "failed CLOSE at CHIP %s", astrom->name);
+	    psFree (view);
+	    return false;
+	}
+    }
+    if (!pmFPAfileClose(astrom, view)) {
+	psError(psErrorCodeLast(), false, "failed CLOSE at FPA %s", astrom->name);
+	psFree (view);
+	return false;
+    }
+
+    // **** convert the header astrometry info (in astrom) to the pmFPA astrometry
+    // structures, saving them on the target fpa structure.
+
+    pmFPAviewReset (view);
+
+    bool bilevelAstrometry = false;
+    pmHDU *phu = pmFPAviewThisPHU(view, astrom->fpa); ///< Astrometry PHU
+    if (phu) {
+        char *ctype = psMetadataLookupStr(NULL, phu->header, "CTYPE1");
+        if (ctype) {
+            bilevelAstrometry = !strcmp(&ctype[4], "-DIS");
+        }
+    }
+    // apply the bilevel astrometry elements to the target
+    if (bilevelAstrometry) {
+      int additional_orders = 4;
+      bool status = false;
+      int config_additional_orders = psMetadataLookupS32(&status,config->arguments,"ADDITIONAL_WCS_ORDERS");
+      if (status) {
+	additional_orders = config_additional_orders;
+      }
+      psMetadataAddS32(astrom->fpa->analysis,PS_LIST_TAIL, "ADDITIONAL_WCS_ORDERS", PS_META_REPLACE,
+		       "Additional orders for bilevel fit.", additional_orders);
+      
+	
+	if (!pmAstromReadBilevelMosaic(astrom->fpa, phu->header)) {
+	    psError(psErrorCodeLast(), false, "Unable to read bilevel mosaic astrometry for skycell.");
+	    psFree(view);
+	    return false;
+	}
+    }
+    psMetadataAddBool (astrom->fpa->analysis, PS_LIST_TAIL, "ASTROMETRY.BILEVEL", PS_META_REPLACE, "bilevel astrometry?", bilevelAstrometry);
+
+    // for pswarpLocalFrame, I need transformations and HDUs on a single fpa (so set on astrom as well as target)
+    // target->fpa->toTPA = psMemIncrRefCounter (astrom->fpa->toTPA);
+    // target->fpa->fromTPA = psMemIncrRefCounter (astrom->fpa->fromTPA);
+    // target->fpa->toSky = psMemIncrRefCounter (astrom->fpa->toSky);
+
+    while ((chip = pmFPAviewNextChip (view, astrom->fpa, 1)) != NULL) {
+	psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+	if (!chip->process || !chip->file_exists) { continue; }
+
+        pmChip *targetChip = pmFPAviewThisChip(view, target->fpa); // < Chip in the output
+	if (!targetChip || !targetChip->process || !targetChip->file_exists) {
+            continue; // only load astrometry into output chips which exist!
+        }
+
+	// we've got the astrom header
+	pmHDU *hdu = pmHDUFromChip(chip); ///< HDU for source
+	if (!hdu || !hdu->header) {
+	    psError(PM_ERR_PROG, false, "Unable to find header for sky cell.");
+	    psFree(view);
+	    return false;
+	}
+
+	if (bilevelAstrometry) {
+	    if (!pmAstromReadBilevelChip(chip, hdu->header)) {
+		psError(psErrorCodeLast(), false, "Unable to read bilevel chip astrometry for skycell.");
+		psFree(view);
+		return false;
+	    }
+	} else {
+	    // we use a default FPA pixel scale of 1.0
+	    if (!pmAstromReadWCS(astrom->fpa, chip, hdu->header, 1.0)) {
+		psError(psErrorCodeLast(), false, "Unable to read WCS astrometry for skycell.");
+		psFree(view);
+		return false;
+	    }
+	}
+	targetChip->toFPA = psMemIncrRefCounter (chip->toFPA);
+	targetChip->fromFPA = psMemIncrRefCounter (chip->fromFPA);
+    }
+
+    // for pswarpLocalFrame, I need transformations and HDUs on a single fpa (so set on astrom as well as target)
+    target->fpa->toTPA = psMemIncrRefCounter (astrom->fpa->toTPA);
+    target->fpa->fromTPA = psMemIncrRefCounter (astrom->fpa->fromTPA);
+    target->fpa->toSky = psMemIncrRefCounter (astrom->fpa->toSky);
+
+    // reset the type to the original value
+    astrom->type = saveType;
+
+    psFree(view);
+    return true;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpLoop.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpLoop.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpLoop.c	(revision 41091)
@@ -0,0 +1,330 @@
+/** @file pswarpLoop.c
+ *
+ *  @brief main processing loop for pswarp
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.38 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-13 21:54:32 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "pswarp.h"
+
+// Loop over the inputs, warp them to the output skycell and then update metadata
+bool pswarpLoop(pmConfig *config, psMetadata *stats)
+{
+
+    // load the recipe
+    bool status = false;
+    psMetadata *recipe = psMetadataLookupPtr (&status, config->recipes, PSWARP_RECIPE);
+    if (!recipe) {
+        psError(PSWARP_ERR_CONFIG, false, "missing recipe %s", PSWARP_RECIPE);
+        return false;
+    }
+
+    // select the input data sources
+    pmFPAfile *output = psMetadataLookupPtr(&status, config->files, "PSWARP.OUTPUT");
+    if (!output) {
+        psError(PSWARP_ERR_CONFIG, true, "Can't find output data!\n");
+        return false;
+    }
+
+    // use the external astrometry source if supplied
+    pmFPAfile *skycell = psMetadataLookupPtr(&status, config->files, "PSWARP.SKYCELL");
+    if (!skycell) {
+        psError(PSWARP_ERR_DATA, true, "Cannot find output astrometry.");
+        return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc(0);
+
+    int nInputs = psMetadataLookupS32(&status, config->arguments, "NUM_INPUTS");
+    if (!status) {
+        psError(PSWARP_ERR_DATA, true, "number of inputs is not defined (programming error)");
+        return false;
+    }
+
+    // load in the input pixel data (ex. background model)
+    pmFPAfileActivate(config->files, false, NULL);
+    pmFPAfileActivate(config->files, true, "PSWARP.INPUT");
+    pmFPAfileActivate(config->files, true, "PSWARP.MASK");
+    pmFPAfileActivate(config->files, true, "PSWARP.VARIANCE");
+
+    // We re-activate the CMF load so we can transform the sources as well as the pixels.
+    // We only need to read in these if the astrometry source is CMF.
+    if (psMetadataLookupBool(&status, recipe, "SOURCES")) {
+	pmFPAfileActivate(config->files, true, "PSWARP.ASTROM");
+    }
+
+    psString refcat = NULL;
+    // loop over this section once per input group
+    for (int i = 0; i < nInputs; i++) {
+	// select the input data sources
+	pmFPAfile *input = pmFPAfileSelectSingle(config->files, "PSWARP.INPUT", i);
+	if (!input) {
+	    psError(PSWARP_ERR_CONFIG, true, "Can't find input data!\n");
+	    return false;
+	}
+
+	// select the input data sources
+	pmFPAfile *astrom = pmFPAfileSelectSingle(config->files, "PSWARP.ASTROM", i);
+	if (!astrom) {
+	    astrom = input;
+	}
+
+	pmFPAviewReset (view);
+
+	// files associated with the science image
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+	    psError(psErrorCodeLast(), false, "Unable to read files.");
+	    goto FAIL;
+	}
+
+	// *** main transformation block 
+	// *** this section loops over the input chips/cells and reads them one at a time
+	// *** the output chips/cells are filled where appropriate, but not yet written to disk 
+	pmChip *chip;
+	while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+	    psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+	    if (!chip->process || !chip->file_exists) { continue; }
+	    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+		psError(psErrorCodeLast(), false, "Unable to read files.");
+		goto FAIL;
+	    }
+
+	    pmCell *cell;
+	    while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+		psTrace ("pswarp", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+		if (!cell->process || !cell->file_exists) { continue; }
+		if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+		    psError(psErrorCodeLast(), false, "Unable to read files.");
+		    goto FAIL;
+		}
+
+		// process each of the readouts
+		pmReadout *readout;
+		while ((readout = pmFPAviewNextReadout(view, input->fpa, 1)) != NULL) {
+		    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+			psError(psErrorCodeLast(), false, "Unable to read files.");
+			goto FAIL;
+		    }
+		    if (!readout->data_exists) {
+			continue;
+		    }
+
+		    // Copy the detections from the astrometry carrier to the input, so they can be accessed by
+		    // pswarpTransformReadout
+		    if (astrom != input) {
+			pmReadout *astromRO = pmFPAviewThisReadout(view, astrom->fpa); // Readout for astrometry
+			pmDetections *detections = psMetadataLookupPtr(&status, astromRO->analysis, "PSPHOT.DETECTIONS"); // Sources from astrometry
+			if (detections) {
+			    psMetadataAddPtr(readout->analysis, PS_LIST_TAIL, "PSPHOT.DETECTIONS", PS_DATA_ARRAY, "Sources from input astrometry", detections);
+			}
+
+			// Determine the reference catalog used
+			if ((!refcat)&&(astromRO)) {
+			  if ((astromRO->parent->parent->hdu->header)&&(output->fpa->analysis)) {
+			    psMetadataItem *refItem = psMetadataLookup(astromRO->parent->parent->hdu->header, "PSREFCAT");
+			    if (refItem) {
+			      refcat = psMetadataLookupStr(NULL, astromRO->parent->parent->hdu->header, "PSREFCAT");
+			      psMetadataAddStr(output->fpa->analysis, PS_LIST_TAIL, "REFERENCE_CATALOG", PS_META_REPLACE,
+					       "Reference catalog used for calibration.", refcat);
+			    }
+			  }
+			}
+			
+		    }
+
+		    pswarpTransformToTarget(output->fpa, readout, config, false);
+		
+		    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+			psError(psErrorCodeLast(), false, "Unable to write files.");
+			goto FAIL;
+		    }
+		}
+		if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+		    psError(psErrorCodeLast(), false, "Unable to write files.");
+		    goto FAIL;
+		}
+	    }
+	    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+		psError(psErrorCodeLast(), false, "Unable to write files.");
+		goto FAIL;
+	    }
+	}
+	if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+	    psError(psErrorCodeLast(), false, "Unable to write files.");
+	    goto FAIL;
+	}
+
+	if (!pswarpUpdateStatistics (output->fpa, stats, input->fpa, astrom->fpa, config)) {
+	    psError(psErrorCodeLast(), false, "problem generating statistics.");
+	    goto FAIL;
+	}
+	if (!pswarpUpdateMetadata (output->fpa, skycell->fpa, input->fpa, astrom->fpa, config, true)) {
+	    psError(psErrorCodeLast(), false, "problem generating statistics.");
+	    goto FAIL;
+	}
+    }
+
+    if (!pswarpMakePSF (config, output, stats)) {
+	psError(psErrorCodeLast(), false, "problem generating PSF.");
+	goto FAIL;
+    }
+
+    psFree(view);
+    return true;
+
+FAIL:
+    psFree (view);
+    return false;
+}
+
+bool pswarpGetBackTransform (pmReadout *tgt, pmReadout *src);
+
+// once the output fpa elements have been built, loop over the fpa and generate stats
+// for each readout
+bool pswarpTransformToTarget (pmFPA *output, pmReadout *input, pmConfig *config, bool backgroundWarp)  {
+
+    pmFPAview *view = pmFPAviewAlloc(0);
+    
+    pmChip *chip;
+    while ((chip = pmFPAviewNextChip (view, output, 1)) != NULL) {
+        psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+        pmCell *cell;
+        while ((cell = pmFPAviewNextCell (view, output, 1)) != NULL) {
+            psTrace ("pswarp", 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
+            pmReadout *readout;
+            while ((readout = pmFPAviewNextReadout(view, output, 1)) != NULL) {
+		pswarpTransformReadout (readout, input, config, backgroundWarp);
+
+		// determine a (rough) linear WCS from readout to input
+		pswarpGetBackTransform (readout, input);
+	    }
+	}
+    }
+    return true;
+}
+
+bool pswarpGetBackTransform (pmReadout *tgt, pmReadout *src) {
+
+    bool status;
+
+    // tgt is the pswarp output target image, src is the input source image
+    // here we are generating a linear transformation from tgt -> src
+
+    // the map is defined for coordinates in the image parent frame.
+    psRegion imageRegion = psRegionSet (0,0,0,0);
+    imageRegion = psRegionForImage (src->image, imageRegion);
+    psString imageRegionString = psRegionToString (imageRegion);
+  
+    // generate a set of points (X,Y)_tgt = (X,Y)_src and do the 2D fit?
+    // save a 2D polynomial, not WCS?
+
+    // use a map with only linear terms in x,y
+    psPlaneTransform *map = psPlaneTransformAlloc(1,1);
+    map->x->coeffMask[1][1] = PS_POLY_MASK_BOTH;
+    map->y->coeffMask[1][1] = PS_POLY_MASK_BOTH;
+
+    pmCell *cell = NULL;
+
+    cell = src->parent;
+    pmChip *chipSrc = cell->parent;
+    pmFPA *fpaSrc = chipSrc->parent;
+
+    cell = tgt->parent;
+    pmChip *chipTgt = cell->parent;
+    pmFPA *fpaTgt = chipTgt->parent;
+
+    psPlane *srcPos = psPlaneAlloc();
+    psPlane *tgtPos = psPlaneAlloc();
+
+    psPlane *FP = psPlaneAlloc();
+    psPlane *TP = psPlaneAlloc();
+    psSphere *sky = psSphereAlloc();
+
+    psVector *tgtX = psVectorAllocEmpty (121, PS_TYPE_F32);
+    psVector *tgtY = psVectorAllocEmpty (121, PS_TYPE_F32);
+    psVector *srcX = psVectorAllocEmpty (121, PS_TYPE_F32);
+    psVector *srcY = psVectorAllocEmpty (121, PS_TYPE_F32);
+
+    int dX = src->image->numCols / 10.0;
+    int dY = src->image->numRows / 10.0;
+    for (int ix = 0; ix < src->image->numCols; ix += dX) {
+	for (int iy = 0; iy < src->image->numRows; iy += dY) {
+
+	    srcPos->x = ix;
+	    srcPos->y = iy;
+
+	    psPlaneTransformApply(FP, chipSrc->toFPA, srcPos);
+	    psPlaneTransformApply (TP, fpaSrc->toTPA, FP);
+	    psDeproject (sky, TP, fpaSrc->toSky);
+
+	    psProject (TP, sky, fpaTgt->toSky);
+	    psPlaneTransformApply (FP, fpaTgt->fromTPA, TP);
+	    psPlaneTransformApply (tgtPos, chipTgt->fromFPA, FP);
+
+	    psVectorAppend (srcX, srcPos->x);
+	    psVectorAppend (srcY, srcPos->y);
+	    psVectorAppend (tgtX, tgtPos->x);
+	    psVectorAppend (tgtY, tgtPos->y);
+	}
+    }
+
+    psVectorFitPolynomial2D(map->x, NULL, 0, srcX, NULL, tgtX, tgtY);
+    psVectorFitPolynomial2D(map->y, NULL, 0, srcY, NULL, tgtX, tgtY);
+
+    // for each output image, we want to add headers corresponding to all input images
+    // save an array of the transformations on the tgt analysis and an array of the input chip names
+    psArray *backmaps = psMetadataLookupPtr (&status, tgt->analysis, PSWARP_ANALYSIS_BACKMAPS);
+    if (!backmaps) {
+	backmaps = psArrayAllocEmpty (4);
+	psMetadataAddArray (tgt->analysis, PS_LIST_TAIL, PSWARP_ANALYSIS_BACKMAPS, 0, "backwards maps", backmaps);
+	psFree (backmaps); // can free here since we put a copy on tgt->analysis
+    }
+    psArrayAdd (backmaps, 4, map);
+    
+    // we also need to save the corresponding chip name for this map
+    char *chipname = psMetadataLookupStr (&status, chipSrc->concepts, "CHIP.NAME");
+    psArray *chipnames = psMetadataLookupPtr (&status, tgt->analysis, PSWARP_ANALYSIS_CHIPNAMES);
+    if (!chipnames) {
+	chipnames = psArrayAllocEmpty (4);
+	psMetadataAddArray (tgt->analysis, PS_LIST_TAIL, PSWARP_ANALYSIS_CHIPNAMES, 0, "source images", chipnames);
+	psFree (chipnames); // can free here since we put a copy on tgt->analysis
+    }
+    psArrayAdd (chipnames, 4, chipname);
+
+    // we also need to save the corresponding chip name for this map
+    psArray *chipRegions = psMetadataLookupPtr (&status, tgt->analysis, PSWARP_ANALYSIS_CHIPREGIONS);
+    if (!chipRegions) {
+	chipRegions = psArrayAllocEmpty (4);
+	psMetadataAddArray (tgt->analysis, PS_LIST_TAIL, PSWARP_ANALYSIS_CHIPREGIONS, 0, "source images", chipRegions);
+	psFree (chipRegions); // can free here since we put a copy on tgt->analysis
+    }
+    psArrayAdd (chipRegions, 4, imageRegionString);
+
+    fprintf (stderr, "warp to %s X = %f + %f x + %f y\n", chipname, map->x->coeff[0][0], map->x->coeff[1][0], map->x->coeff[0][1]);
+    fprintf (stderr, "warp to %s Y = %f + %f x + %f y\n", chipname, map->y->coeff[0][0], map->y->coeff[1][0], map->y->coeff[0][1]);
+    
+    psFree (map);
+    psFree (tgtX);
+    psFree (tgtY);
+    psFree (srcX);
+    psFree (srcY);
+
+    psFree (srcPos);
+    psFree (tgtPos);
+    psFree (FP);
+    psFree (TP);
+    psFree (sky);
+
+    psFree (imageRegionString);
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpLoopBackground.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpLoopBackground.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpLoopBackground.c	(revision 41091)
@@ -0,0 +1,291 @@
+/** @file pswarpLoopBackground.c
+ *
+ *  @brief background model processing loop for pswarp
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.38 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-13 21:54:32 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "pswarp.h"
+
+#define WCS_NONLIN_TOL 0.001            // Non-linear tolerance for header WCS
+#define TESTING 0                       // Testing output?
+
+// Loop over the inputs, warp them to the output skycell and then write out the output.
+bool pswarpLoopBackground(pmConfig *config, psMetadata *stats)
+{
+    bool status;
+    bool mdok;                          // Status of MD lookup
+
+    if (!psMetadataLookupBool(NULL,config->arguments,"BACKGROUND.MODEL")) return true;
+
+    int xGrid = psMetadataLookupS32(&mdok,config->arguments,"BKG.XGRID");
+    int yGrid = psMetadataLookupS32(&mdok,config->arguments,"BKG.YGRID");
+
+    // WHAT IS THIS?? should it be disabled at the end?
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "INTERPOLATION.MODE", PS_META_REPLACE, "", 8);
+
+    // load the recipe
+    psMetadata *recipe = psMetadataLookupPtr (&status, config->recipes, PSWARP_RECIPE);
+    if (!recipe) {
+        psError(PSWARP_ERR_CONFIG, false, "missing recipe %s", PSWARP_RECIPE);
+        return false;
+    }
+
+    // select the input data background model
+    pmFPAfile *output = psMetadataLookupPtr(&status, config->files, "PSWARP.OUTPUT.BKGMODEL");
+    if (!output) {
+        psError(PSWARP_ERR_CONFIG, true, "Can't find output data!\n");
+        return false;
+    }
+
+    // use the external astrometry source if supplied
+    pmFPAfile *skycell = psMetadataLookupPtr(&status, config->files, "PSWARP.SKYCELL");
+    if (!skycell) {
+        psError(PSWARP_ERR_DATA, true, "Cannot find output astrometry.");
+        return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc(0);
+
+    int nInputs = psMetadataLookupS32(&status, config->arguments, "NUM_INPUTS");
+    if (!status) {
+        psError(PSWARP_ERR_DATA, true, "number of inputs is not defined (programming error)");
+        return false;
+    }
+
+    // we are only reading the background models, not writing anything
+    pmFPAfileActivate(config->files, false, NULL);
+    pmFPAfileActivate(config->files, true, "PSWARP.BKGMODEL");
+
+    psString refcat = NULL;
+    // loop over this section once per input group
+    for (int i = 0; i < nInputs; i++) {
+
+	// select the input data source : we are reading & transforming pixels for the background model
+	pmFPAfile *input = pmFPAfileSelectSingle(config->files, "PSWARP.BKGMODEL", i);
+	if (!input) {
+	    psError(PSWARP_ERR_CONFIG, true, "Can't find input data!\n");
+	    return false;
+	}
+
+	// select the input data sources
+	pmFPAfile *astrom = pmFPAfileSelectSingle(config->files, "PSWARP.ASTROM", i);
+	if (!astrom) {
+	    astrom = input;
+	}
+
+	// check input astrometry mode
+	bool bilevelAstrometry = psMetadataLookupBool (NULL, astrom->fpa->analysis, "ASTROMETRY.BILEVEL");
+	if (bilevelAstrometry) {
+	    // top-level elements of the input model astrometry
+	    input->fpa->toTPA = psMemIncrRefCounter (astrom->fpa->toTPA);
+	    input->fpa->fromTPA = psMemIncrRefCounter (astrom->fpa->fromTPA);
+	    input->fpa->toSky = psMemIncrRefCounter (astrom->fpa->toSky);
+	}
+
+	pmFPAviewReset (view);
+
+	pmFPAview *view = pmFPAviewAlloc(0);
+	if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+	    psError(psErrorCodeLast(), false, "Unable to read files.");
+	    goto FAIL;
+	}
+
+	pmChip *chip;
+	while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+	    if (!chip->process || !chip->file_exists) { continue; }
+	    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+		psError(psErrorCodeLast(), false, "Unable to read files.");
+		goto FAIL;
+	    }
+
+	    // adjust output astrometry
+	    double xBin = NAN;
+	    double yBin = NAN;
+	    pswarpGetInputScales (&xBin, &yBin, config, view, chip);
+	    double fluxRatio = xBin * yBin / (float) (xGrid * yGrid);
+	    
+	    if (!pswarpModifyChipAstrom (config, view, chip, astrom, bilevelAstrometry, xBin, yBin)) {
+		psError(psErrorCodeLast(), false, "failed to set BKGMODEL astrometry.");
+		goto FAIL;
+	    }
+
+	    pmCell *cell;
+	    while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+		psTrace ("pswarp", 4, "ACell %d: %x %x %d\n", view->cell, cell->file_exists, cell->process,psErrorCodeLast());
+		if (!cell->process || !cell->file_exists) { continue; }
+		if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+		    psError(psErrorCodeLast(), false, "Unable to read files.");
+		    goto FAIL;
+		}
+
+		// process each of the readouts
+		pmReadout *readout;
+		while ((readout = pmFPAviewNextReadout(view, input->fpa, 1)) != NULL) {
+		    if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+			psError(psErrorCodeLast(), false, "Unable to read files.");
+			goto FAIL;
+		    }
+		    if (!readout->data_exists) {
+			continue;
+		    }
+
+		    if (astrom != input) {
+		      pmReadout *astromRO = pmFPAviewThisReadout(view, astrom->fpa); // Readout for astrometry
+		      if ((!refcat)&&(astromRO)) {
+			if ((astromRO->parent->parent->hdu->header)&&(output->fpa->analysis)) {
+			  psMetadataItem *refItem = psMetadataLookup(astromRO->parent->parent->hdu->header, "PSREFCAT");
+			  if (refItem) {
+			    refcat = psMetadataLookupStr(NULL, astromRO->parent->parent->hdu->header, "PSREFCAT");
+			    psMetadataAddStr(output->fpa->analysis, PS_LIST_TAIL, "REFERENCE_CATALOG", PS_META_REPLACE,
+					     "Reference catalog used for calibration.", refcat);
+			  }
+			}
+		      }
+		    }
+		    
+		    // re-normalize the BKGMODEL pixels by modified astrometry
+		    for (int x = 0; x < readout->image->numCols; x++) {
+			for (int y = 0; y < readout->image->numRows; y++) {
+			    readout->image->data.F32[y][x] *= fluxRatio;
+			}
+		    }
+		
+		    // transform the actual BKGMODEL pixels from readout to output
+		    pswarpTransformToTarget (output->fpa, readout, config, true);
+
+		    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) { // Readout
+			psError(psErrorCodeLast(), false, "Unable to close files.");
+			goto FAIL;
+		    }
+		}
+
+		if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) { // Cell
+		    psError(psErrorCodeLast(), false, "Unable to close files.");
+		    goto FAIL;
+		}
+	    }
+
+	    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) { // Chip
+		psError(psErrorCodeLast(), false, "Unable to close files.");
+		goto FAIL;
+	    }
+	}
+
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) { // FPA
+	    psError(psErrorCodeLast(), false, "Unable to close files.");
+	    goto FAIL;
+	}
+
+	if (!pswarpUpdateMetadata (output->fpa, skycell->fpa, input->fpa, astrom->fpa, config, false)) {
+	    psError(psErrorCodeLast(), false, "problem generating statistics.");
+	    goto FAIL;
+	}
+    }
+
+    psFree (view);
+    return true;
+
+FAIL:
+    psFree (view);
+    return false;
+}
+
+// **** input == (small) BKGMODEL, astrom == (fullsize) ASTROM
+// use astrometry from input ref astrometry and BINNING info from input model to modify the astrometry 
+// elements of input chip.  scale is the area of an input pixel is raw image pixels
+bool pswarpGetInputScales (double *xBin, double *yBin, pmConfig *config, pmFPAview *view, pmChip *chip) {
+
+    // Pull information from the header of the background files so we can use it to set values.
+    pmHDU *hdu = pmFPAviewThisHDU(view, chip->parent);
+    psMetadata *header = hdu->header;
+	
+    int IMAXIS1 = psMetadataLookupS32(NULL,header,"IMNAXIS1");
+    int IMAXIS2 = psMetadataLookupS32(NULL,header,"IMNAXIS2");
+    int NAXIS1 = psMetadataLookupS32(NULL,header,"NAXIS1");
+    int NAXIS2 = psMetadataLookupS32(NULL,header,"NAXIS2");
+    char *CCDSUM = psMetadataLookupStr(NULL,header,"CCDSUM");
+    int CCDSUM1 = atoi(strtok(CCDSUM," "));
+    int CCDSUM2 = atoi(strtok(NULL," "));
+	
+    psMetadataAddF32(config->arguments,PS_LIST_TAIL,"BKG_WARP_XOFFSET", PS_META_REPLACE, "xoffset for background model data", (NAXIS1 * CCDSUM1 - IMAXIS1) / (2.0 * CCDSUM1));
+    psMetadataAddF32(config->arguments,PS_LIST_TAIL,"BKG_WARP_YOFFSET", PS_META_REPLACE, "yoffset for background model data", (NAXIS2 * CCDSUM2 - IMAXIS2) / (2.0 * CCDSUM2));
+
+    psTrace("pswarp",5,"%d %d %d %d %d %d %g %g %d %d",
+	    psMetadataLookupS32(NULL,header,"IMNAXIS1"),
+	    psMetadataLookupS32(NULL,header,"IMNAXIS2"),
+	    psMetadataLookupS32(NULL,header,"NAXIS1"),
+	    psMetadataLookupS32(NULL,header,"NAXIS2"),
+	    CCDSUM1,CCDSUM2,
+	    psMetadataLookupF32(NULL,config->arguments,"BKG_WARP_XOFFSET"),
+	    psMetadataLookupF32(NULL,config->arguments,"BKG_WARP_YOFFSET"),
+	    psMetadataLookupS32(NULL,config->arguments,"BKG.XGRID"),
+	    psMetadataLookupS32(NULL,config->arguments,"BKG.YGRID"));
+
+    *xBin = CCDSUM1;
+    *yBin = CCDSUM2;
+
+    return true;
+}
+
+// **** input == (small) BKGMODEL, astrom == (fullsize) ASTROM
+// use astrometry from input ref astrometry and BINNING info from input model to modify the astrometry 
+// elements of input chip.  scale is the area of an input pixel is raw image pixels
+bool pswarpModifyChipAstrom (pmConfig *config, pmFPAview *view, pmChip *chip, pmFPAfile *astrom, bool bilevelAstrometry, double xBin, double yBin) {
+
+    // read WCS data from the corresponding header 
+    pmHDU *hdu = pmFPAviewThisHDU (view, astrom->fpa);
+
+    // generate a WCS structure from the ASTROM header keywords
+    pmAstromWCS *WCS = pmAstromWCSfromHeader(hdu->header);
+
+    // re-scale the terms of the WCS to match the BKGMODEL scale
+    double cd1f = xBin;
+    double cd2f = yBin;
+
+    WCS->cdelt1 *= cd1f;
+    WCS->cdelt2 *= cd2f;
+    WCS->crpix1 = WCS->crpix1 / cd1f;
+    WCS->crpix2 = WCS->crpix2 / cd2f;
+
+    // WCS->trans->x->nX/nY
+    // adjust the scale of the WCS coeffs
+    for (int q = 0; q <= WCS->trans->x->nX; q++) {
+	for (int r = 0; r <= WCS->trans->x->nY; r++) {
+	    WCS->trans->x->coeff[q][r] *= pow(cd1f,q) * pow(cd2f,r);
+	}
+    }
+    for (int q = 0; q <= WCS->trans->y->nX; q++) {
+	for (int r = 0; r <= WCS->trans->y->nY; r++) {
+	    WCS->trans->y->coeff[q][r] *= pow(cd1f,q) * pow(cd2f,r);
+	}
+    }
+
+    // this is a bit crude : use a locally temporary header to store the WCS information
+    // which is then applied to the input chip (why not modify the input chip header?)
+    psMetadata *tmpHeader = psMetadataAlloc();
+
+    // write the modified WCS to the header
+    // XXX this should probably not modify the astrom header
+    pmAstromWCStoHeader (tmpHeader, WCS);
+	
+    // use the modified header to update the WCS elements of the BKGMODEL fpa/chip structures
+    if (bilevelAstrometry) {
+	if (!pmAstromReadBilevelChip (chip, tmpHeader)) {
+	    psError(psErrorCodeLast(), false, "Unable to read bilevel chip astrometry for input FPA.");
+	    return false;
+	}
+    } else {
+	// we use a default FPA pixel scale of 1.0
+	if (!pmAstromReadWCS (chip->parent, chip, tmpHeader, 1.0)) {
+	    psError(psErrorCodeLast(), false, "Unable to read WCS astrometry for input FPA.");
+	    return false;
+	}
+    }
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpLoopSkycell.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpLoopSkycell.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpLoopSkycell.c	(revision 41091)
@@ -0,0 +1,447 @@
+/** @file pswarpLoop.c
+ *
+ *  @brief mail processing loop for pswarp
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.38 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-13 21:54:32 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "pswarp.h"
+#include <ppStats.h>
+#include "pswarpFileNames.h"            // Lists of file rules used at different stages
+
+#define WCS_NONLIN_TOL 0.001            // Non-linear tolerance for header WCS
+#define TESTING 0                       // Testing output?
+
+// Loop over the inputs, warp them to the output skycell and then write out the output.
+bool pswarpLoopSkycell(pmConfig *config, psMetadata *stats)
+{
+    bool status;
+    bool mdok;                          // Status of MD lookup
+
+    const char *skyCamera = psMetadataLookupStr(NULL, config->arguments,
+                                                "SKYCELL.CAMERA");  // Name of camera for skycell
+    pmConfigCamerasCull(config, skyCamera);
+    pmConfigRecipesCull(config, "PSWARP,PPSTATS,PSPHOT,PSASTRO,MASKS,JPEG");
+
+    // load the recipe
+    psMetadata *recipe = psMetadataLookupPtr (&status, config->recipes, PSWARP_RECIPE);
+    if (!recipe) {
+        psError(PSWARP_ERR_CONFIG, false, "missing recipe %s", PSWARP_RECIPE);
+        return false;
+    }
+
+    if (!pswarpSetMaskBits(config)) {
+        psError(psErrorCodeLast(), false, "failed to set mask bits");
+        return NULL;
+    }
+
+    // output mask bits
+    psImageMaskType maskValue = psMetadataLookupImageMask(&status, recipe, "MASK.OUTPUT");
+    psAssert (status, "MASK.OUTPUT was not defined");
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr(NULL, config->files, "PSWARP.INPUT");
+    if (!input) {
+        psError(PSWARP_ERR_CONFIG, true, "Can't find input data!\n");
+        return false;
+    }
+
+    // use the external astrometry source if supplied
+    pmFPAfile *astrom = psMetadataLookupPtr(NULL, config->files, "PSWARP.ASTROM");
+    if (!astrom) {
+        astrom = input;
+    }
+
+    if (astrom->camera != input->camera) {
+        psError(PSWARP_ERR_DATA, true, "Input camera and astrometry camera do not match.");
+        return false;
+    }
+
+    // select the output readout
+    pmFPAview *view = pmFPAviewAlloc(0);
+    view->chip = 0;
+    view->cell = 0;
+    view->readout = 0;
+    pmReadout *output = pmFPAfileThisReadout(config->files, view, "PSWARP.OUTPUT");
+    if (!output) {
+        psError(PSWARP_ERR_CONFIG, true, "Can't find output data!\n");
+        return false;
+    }
+    psFree (view);
+
+
+    // Turn all skycell files on to generate them, and then turn them off for the loop over the input images
+    // the input, which is in a different format.
+    // XXX why is this being done here, doesn't it duplicate the work in pswarpDefine.c??
+    pswarpFileActivation(config, detectorFiles, false);
+    pswarpFileActivation(config, photFiles, false);
+    pswarpFileActivation(config, independentFiles, false);
+    pswarpFileActivation(config, skycellFiles, false);
+    
+    // pswarpFileActivation(config, skycellFiles, true);
+    // if (0) {
+    //   if (!pswarpIOChecksBefore(config)) {
+    //     psError(psErrorCodeLast(), false, "Unable to read files.");
+    //     goto DONE;
+    //   }
+    // }
+   
+    // Read the input astrometry
+    // XXX rather than use the activations here, this should just explicitly loop over the desired filerule
+    {
+        pmFPAfileActivate(config->files, true, "PSWARP.ASTROM");
+
+        pmChip *chip;
+        pmFPAview *view = pmFPAviewAlloc(0);
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+            psError(psErrorCodeLast(), false, "Unable to read files.");
+            goto DONE;
+        }
+        while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+            psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+            if (!chip->process || !chip->file_exists) { continue; }
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                psError(psErrorCodeLast(), false, "Unable to read files.");
+                goto DONE;
+            }
+            pmCell *cell;
+            while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+                psTrace ("pswarp", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+                if (!cell->process || !cell->file_exists) { continue; }
+                if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE) ||
+                    !pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) {
+                    psError(psErrorCodeLast(), false, "Unable to read files.");
+                    goto DONE;
+                }
+            }
+            if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) {
+                psError(psErrorCodeLast(), false, "Unable to write files.");
+                goto DONE;
+            }
+        }
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) {
+            psError(psErrorCodeLast(), false, "Unable to write files.");
+            goto DONE;
+        }
+        psFree(view);
+
+        pswarpFileActivation(config, detectorFiles, true);
+        pmFPAfileActivate(config->files, false, "PSWARP.ASTROM");
+    }
+
+    // Turn on the source output --- we need to get rid of these so that we can measure the PSF
+    pmFPAfileActivate(config->files, true, "PSWARP.OUTPUT.SOURCES");
+
+    // Don't care about the skycell anymore --- we've read it, and that's all we need to do.
+    pmFPAfileActivate(config->files, false, "PSWARP.SKYCELL");
+    view = pmFPAviewAlloc(0);
+
+    // find the FPA phu
+    bool bilevelAstrometry = false;
+    pmHDU *phu = pmFPAviewThisPHU(view, astrom->fpa);
+    if (phu) {
+        char *ctype = psMetadataLookupStr(NULL, phu->header, "CTYPE1");
+        if (ctype) {
+            bilevelAstrometry = !strcmp (&ctype[4], "-DIS");
+        }
+    }
+    if (bilevelAstrometry) {
+        if (!pmAstromReadBilevelMosaic(input->fpa, phu->header)) {
+            psError(psErrorCodeLast(), false, "Unable to read bilevel mosaic astrometry for input FPA.");
+            psFree(view);
+            goto DONE;
+        }
+    }
+
+    psList *cells = psListAlloc(NULL);  // List of cells, for concepts averaging
+
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+        psError(psErrorCodeLast(), false, "Unable to read files.");
+        goto DONE;
+    }
+
+    // *** main transformation block
+    pmChip *chip;
+    while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+        psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+            psError(psErrorCodeLast(), false, "Unable to read files.");
+            goto DONE;
+        }
+
+        // read WCS data from the corresponding header
+        pmHDU *hdu = pmFPAviewThisHDU (view, astrom->fpa);
+
+	
+        if (bilevelAstrometry) {
+            if (!pmAstromReadBilevelChip (chip, hdu->header)) {
+                psError(psErrorCodeLast(), false, "Unable to read bilevel chip astrometry for input FPA.");
+                psFree(view);
+                goto DONE;
+            }
+        } else {
+            // we use a default FPA pixel scale of 1.0
+            if (!pmAstromReadWCS (input->fpa, chip, hdu->header, 1.0)) {
+                psError(psErrorCodeLast(), false, "Unable to read WCS astrometry for input FPA.");
+                psFree(view);
+                goto DONE;
+            }
+        }
+	
+        pmCell *cell;
+        while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psTrace ("pswarp", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+            if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+                psError(psErrorCodeLast(), false, "Unable to read files.");
+                goto DONE;
+            }
+
+            psListAdd(cells, PS_LIST_TAIL, cell);
+
+            // process each of the readouts
+            pmReadout *readout;
+            while ((readout = pmFPAviewNextReadout(view, input->fpa, 1)) != NULL) {
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+                    psError(psErrorCodeLast(), false, "Unable to read files.");
+                    goto DONE;
+                }
+                if (!readout->data_exists) {
+                    continue;
+                }
+
+                // Copy the detections from the astrometry carrier to the input, so they can be accessed by
+                // pswarpTransformReadout
+                pmReadout *astromRO = pmFPAviewThisReadout(view, astrom->fpa); // Readout for astrometry
+                pmDetections *detections = psMetadataLookupPtr(&mdok, astromRO->analysis, "PSPHOT.DETECTIONS"); // Sources from astrometry
+                if (detections) {
+                    psMetadataAddPtr(readout->analysis, PS_LIST_TAIL, "PSPHOT.DETECTIONS", PS_DATA_ARRAY, "Sources from input astrometry", detections);
+                }
+
+                pswarpTransformReadout(output, readout, config);
+		
+                if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+                    psError(psErrorCodeLast(), false, "Unable to write files.");
+                    goto DONE;
+                }
+            }
+            if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+                psError(psErrorCodeLast(), false, "Unable to write files.");
+                goto DONE;
+            }
+        }
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+            psError(psErrorCodeLast(), false, "Unable to write files.");
+            goto DONE;
+        }
+    }
+
+    if (!output->data_exists) {
+        psWarning("No overlap between input and skycell.");
+        if (stats) {
+            psMetadataAddS32(stats, PS_LIST_TAIL, "QUALITY", PS_META_REPLACE,
+                             "No overlap between input and skycell", PSWARP_ERR_NO_OVERLAP);
+        }
+        psphotFilesActivate(config, false);
+        psFree(cells);
+        psFree(view);
+        goto DONE;
+    }
+    
+    pmCell *outCell = output->parent;   ///< Output cell
+    pmChip *outChip = outCell->parent;  ///< Output chip
+    pmFPA *outFPA = outChip->parent;    ///< Output FP
+
+    if (!pswarpPixelsLit(output, stats, config)) {
+        psError(psErrorCodeLast(), false, "Unable to calculate pixel regions.");
+        psFree(cells);
+        psFree(view);
+        goto DONE;
+    }
+    bool doStats = psMetadataLookupBool(&mdok,recipe,"MASK.STATS");
+    if (doStats) {
+      if (!pswarpMaskStats(output, stats, config)) {
+	psError(psErrorCodeLast(), false, "Unable to calculate mask stats.");
+	psFree(cells);
+	psFree(view);
+	goto DONE;
+      }
+    }
+    // Set covariance matrix for output
+    {
+        psList *covariances = psMetadataLookupPtr(&mdok, output->analysis,
+                                                  PSWARP_ANALYSIS_COVARIANCES); // Covariance matrices
+        psAssert(covariances, "Should be there");
+        psArray *covars = psListToArray(covariances); // Array of covariance matrices
+        psKernel *covar = psImageCovarianceAverage(covars);
+        psFree(covars);
+        psMetadataRemoveKey(output->analysis, PSWARP_ANALYSIS_COVARIANCES);
+
+        // Correct covariance matrix scale for the mean (square root of the) Jacobian
+        double jacobian = psMetadataLookupF64(NULL, output->analysis, PSWARP_ANALYSIS_JACOBIAN); // Jacobian
+        int goodPixels = psMetadataLookupS32(NULL, output->analysis, PSWARP_ANALYSIS_GOODPIX);   // Good pixels
+        jacobian /= goodPixels;
+        output->covariance = psImageCovarianceScale(covar, jacobian);
+        psFree(covar);
+
+        if (output->variance) {
+            psImageCovarianceTransfer(output->variance, output->covariance);
+        }
+    }
+
+    if (!pmConceptsAverageCells(outCell, cells, NULL, NULL, false)) {
+        psError(psErrorCodeLast(), false, "Unable to average cell concepts.");
+        psFree(cells);
+        psFree(view);
+        goto DONE;
+    }
+    psFree(cells);
+
+    psRegion *trimsec = psMetadataLookupPtr(NULL, outCell->concepts, "CELL.TRIMSEC"); ///< Trim section
+    trimsec->x0 = trimsec->x1 = trimsec->y0 = trimsec->y1 = 0; ///< All pixels
+
+    if (!psMetadataCopy(outFPA->concepts, input->fpa->concepts)) {
+        psError(psErrorCodeLast(), false, "Unable to copy FPA concepts from input to output.");
+        psFree(view);
+        goto DONE;
+    }
+
+    // Update ZP from the astrometry
+    {
+        psMetadataItem *item = psMetadataLookup(outFPA->concepts, "FPA.ZP");
+        item->data.F32 = psMetadataLookupF32(NULL, astrom->fpa->concepts, "FPA.ZP");
+    }
+
+    pmHDU *hdu = outFPA->hdu;           ///< HDU for the output warped image
+
+    // Copy header from target
+    {
+        pmFPAview *skyView = pmFPAviewAlloc(0); ///< View into skycell
+        skyView->chip = skyView->cell = 0;
+        pmCell *cell = pmFPAfileThisCell(config->files, skyView, "PSWARP.SKYCELL"); // Skycell cell
+        psFree(skyView);
+        pmHDU *skyHDU = pmHDUFromCell(cell); ///< HDU
+        if (!skyHDU) {
+            psError(PSWARP_ERR_DATA, false, "Unable to find skycell HDU.");
+            psFree(view);
+            goto DONE;
+        }
+        hdu->header = psMetadataCopy(hdu->header, skyHDU->header);
+    }
+
+    pswarpVersionHeader(hdu->header);
+    
+    if (!pmAstromWriteWCS(hdu->header, outFPA, outChip, WCS_NONLIN_TOL)) {
+        psError(psErrorCodeLast(), false, "Unable to generate WCS header.");
+        goto DONE;
+    }
+
+    if (!pmFPAfileIOChecks(config, view, PM_FPA_AFTER)) {
+        psError(psErrorCodeLast(), false, "Unable to write files.");
+        goto DONE;
+    }
+
+    // Done with the detector side of things
+    pswarpFileActivation(config, detectorFiles, false);
+    pswarpFileActivation(config, independentFiles, false);
+
+
+    // We need a new PSF model for the warped frame.  It would be good to generate this analytically, but
+    // that's going to be tricky.  We have a list of sources, so we use those to redetermine the PSF model.
+
+    if (psMetadataLookupBool(&mdok, recipe, "PSF")) {
+        pswarpFileActivation(config, photFiles, true);
+        if (!pswarpIOChecksBefore(config)) {
+            psError(psErrorCodeLast(), false, "Unable to read files.");
+            goto DONE;
+        }
+
+        // supply the readout and fpa of interest to psphot
+        pmFPAfile *photFile = psMetadataLookupPtr(NULL, config->files, "PSPHOT.INPUT");
+        pmFPACopy(photFile->fpa, outFPA);
+
+        pmFPAview *view = pmFPAviewAlloc(0); ///< View into skycell
+        view->chip = view->cell = view->readout = 0;
+
+        // grab the sources of interest from the storage location (pmFPAfile PSPHOT.INPUT.CMF)
+        psArray *sources = psphotLoadPSFSources (config, view);
+        if (!sources) {
+            psError(psErrorCodeLast(), false, "No sources supplied to measure PSF");
+            goto DONE;
+        }
+
+        pmModelClassSetLimits(PM_MODEL_LIMITS_STRICT);
+
+        // measure the PSF using these sources
+        if (!psphotReadoutFindPSF(config, view, "PSPHOT.INPUT", sources)) {
+            // This is likely a data quality issue
+            // XXX Split into multiple cases using error codes?
+            psErrorStackPrint(stderr, "Unable to determine PSF");
+            psWarning("Unable to determine PSF --- suspect bad data quality.");
+            if (stats && psMetadataLookupS32(NULL, stats, "QUALITY") == 0) {
+                psMetadataAddS32(stats, PS_LIST_TAIL, "QUALITY", PS_META_REPLACE,
+                                 "Unable to determine PSF", psErrorCodeLast());
+            }
+            psErrorClear();
+            psphotFilesActivate(config, false);
+        }
+
+        // Ensure seeing is carried over
+        pmChip *photChip = pmFPAviewThisChip(view, photFile->fpa);                 // Chip with seeing
+        psMetadataItem *item = psMetadataLookup(outChip->concepts, "CHIP.SEEING"); // Concept with seeing
+        item->data.F32 = psMetadataLookupF32(NULL, photChip->concepts, "CHIP.SEEING");
+
+// XXX EAM : put this in a visualization function
+#if (TESTING)
+        {
+            #define PSF_SIZE 20         ///< Half-size of PSF
+            #define PSF_FLUX 10000      ///< Central flux for PSF
+            pmChip *photChip = pmFPAviewThisChip(view, photFile->fpa);
+            pmPSF *psf = psMetadataLookupPtr(NULL, photChip->analysis, "PSPHOT.PSF");
+            psImage *image = psImageAlloc(2 * PSF_SIZE + 1, 2 * PSF_SIZE + 1, PS_TYPE_F32);
+            psImageInit(image, 0);
+            pmModel *model = pmModelFromPSFforXY(psf, PSF_SIZE, PSF_SIZE, PSF_FLUX);
+            pmModelAdd(image, NULL, model, PM_MODEL_OP_FULL, 0);
+            psFree(model);
+            psFits *fits = psFitsOpen("psf.fits", "w");
+            psFitsWriteImage(fits, NULL, image, 0, NULL);
+            psFitsClose(fits);
+            psFree(image);
+        }
+#endif
+
+        psFree(view);
+    }
+
+    // Perform statistics on the output image
+    if (stats) {
+        if (!ppStatsFPA(stats, output->parent->parent->parent, view, maskValue, config)) {
+            psWarning("Unable to perform statistics on warped image.");
+        }
+    }
+    
+
+    // Add MD5 information for readout
+    const char *chipName = psMetadataLookupStr(NULL, output->parent->parent->concepts, "CHIP.NAME");
+    const char *cellName = psMetadataLookupStr(NULL, output->parent->concepts, "CELL.NAME");
+    psString headerName = NULL; ///< Header name for MD5
+    psStringAppend(&headerName, "MD5_%s_%s_%d", chipName, cellName, view->readout);
+    psVector *md5 = psImageMD5(output->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);
+
+ DONE:
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpMakePSF.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpMakePSF.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpMakePSF.c	(revision 41091)
@@ -0,0 +1,112 @@
+/** @file pswarpLoop.c
+ *
+ *  @brief mail processing loop for pswarp
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.38 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-13 21:54:32 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "pswarp.h"
+
+// We need a new PSF model for the warped frame.  It would be good to generate this analytically, but
+// that's going to be tricky.  We have a list of sources, so we use those to redetermine the PSF model.
+
+bool pswarpMakePSF (pmConfig *config, pmFPAfile *output, psMetadata *stats) {
+
+    bool status = false;
+
+    // load the recipe
+    psMetadata *recipe = psMetadataLookupPtr (&status, config->recipes, PSWARP_RECIPE);
+    if (!recipe) {
+        psError(PSWARP_ERR_CONFIG, false, "missing recipe %s", PSWARP_RECIPE);
+        return false;
+    }
+
+    if (!psMetadataLookupBool(&status, recipe, "PSF")) {
+	psLogMsg("pswarp", PS_LOG_INFO, "Skipping PSF measurement");
+	return true;
+    }
+
+    // XXX move above the loop
+    pmModelClassSetLimits(PM_MODEL_LIMITS_STRICT);
+		
+    // supply the readout and fpa of interest to psphot
+    pmFPAfile *photFile = psMetadataLookupPtr(&status, config->files, "PSPHOT.INPUT");
+    pmFPACopy(photFile->fpa, output->fpa);
+
+    pmFPAview *view = pmFPAviewAlloc(0); ///< View into skycell
+
+    pmChip *chip;
+    while ((chip = pmFPAviewNextChip (view, photFile->fpa, 1)) != NULL) {
+        psTrace ("pswarpMakePSF", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+        pmCell *cell;
+        while ((cell = pmFPAviewNextCell (view, photFile->fpa, 1)) != NULL) {
+            psTrace ("pswarpMakePSF", 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
+            pmReadout *readout;
+            while ((readout = pmFPAviewNextReadout(view, photFile->fpa, 1)) != NULL) {
+                if (!readout->data_exists) {
+                    continue;
+                }
+
+		// grab the sources of interest from the storage location (pmFPAfile PSPHOT.INPUT.CMF)
+		psArray *sources = psphotLoadPSFSources (config, view);
+		if (!sources) {
+		    // psError(psErrorCodeLast(), false, "No sources supplied to measure PSF");
+		    psLogMsg ("psphot", 3, "no psf sources for this readout (%d %d %d)", view->chip, view->cell, view->readout);
+		    continue;
+		}
+
+		// measure the PSF using these sources
+		if (!psphotReadoutFindPSF(config, view, "PSPHOT.INPUT", sources)) {
+		    // A failure is likely a data quality issue.  raise a quality flag, but do not skip other readouts
+		    // XXX Split into multiple cases using error codes?
+		    psErrorStackPrint(stderr, "Unable to determine PSF for readout (%d %d %d)", view->chip, view->cell, view->readout);
+		    psWarning("Unable to determine PSF --- suspect bad data quality : readout (%d %d %d)", view->chip, view->cell, view->readout);
+		    if (stats) {
+			int currentQuality = psMetadataLookupS32(&status, stats, "QUALITY");
+			if (currentQuality == 0) {
+			    psMetadataAddS32(stats, PS_LIST_TAIL, "QUALITY", PS_META_REPLACE, "Unable to determine PSF", psErrorCodeLast());
+			}
+		    }
+		    psErrorClear();
+		}
+	    }
+	}
+
+	// Ensure seeing is carried over from the PSPHOT.INPUT chip to the output chip
+	pmChip *outChip = pmFPAviewThisChip(view, output->fpa); // Chip with seeing
+	psMetadataItem *item = psMetadataLookup(outChip->concepts, "CHIP.SEEING"); // Concept with seeing
+	item->data.F32 = psMetadataLookupF32(&status, chip->concepts, "CHIP.SEEING");
+    }
+
+    psFree(view);
+    return true;
+}
+
+# if (0)
+bool pswarpMakePSF_test () {
+
+#define PSF_SIZE 20         ///< Half-size of PSF
+#define PSF_FLUX 10000      ///< Central flux for PSF
+    pmChip *photChip = pmFPAviewThisChip(view, photFile->fpa);
+    pmPSF *psf = psMetadataLookupPtr(NULL, photChip->analysis, "PSPHOT.PSF");
+    psImage *image = psImageAlloc(2 * PSF_SIZE + 1, 2 * PSF_SIZE + 1, PS_TYPE_F32);
+    psImageInit(image, 0);
+    pmModel *model = pmModelFromPSFforXY(psf, PSF_SIZE, PSF_SIZE, PSF_FLUX);
+    pmModelAdd(image, NULL, model, PM_MODEL_OP_FULL, 0);
+    psFree(model);
+    psFits *fits = psFitsOpen("psf.fits", "w");
+    psFitsWriteImage(fits, NULL, image, 0, NULL);
+    psFitsClose(fits);
+    psFree(image);
+    return true;
+}
+# endif
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpMapGrid.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpMapGrid.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpMapGrid.c	(revision 41091)
@@ -0,0 +1,267 @@
+/** @file pswarpMapGrid.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-05 20:44:04 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "pswarp.h"
+
+/**
+ * pswarpMapGridFromImage builds a set (a grid) of locally-linear maps which convert the source
+ * coordinates (src) to destination coordinates (dest).  we construct a grid with superpixel
+ * spacing of nXpix, nYpix.  The transformation for each grid cell is valid for the superpixel.
+ * The grid over-fills the source image so every source image pixel is guaranteed to have a map.
+ */
+pswarpMapGrid *pswarpMapGridFromImage (pmReadout *dest, pmReadout *src, int nXpix, int nYpix) {
+
+    int i, ni;
+    int j, nj;
+
+    // start counting from the center of the superpixels
+    double xMin = 0.5*nXpix;
+    double yMin = 0.5*nYpix;
+
+    // the map is defined for coordinates in the image parent frame.
+    int Nx = src->image->numCols + src->image->col0;
+    int Ny = src->image->numRows + src->image->row0;
+
+    // always allocate an extra superpixel to handle spillover
+    int nXpts = (int)(Nx / nXpix) + 1;
+    int nYpts = (int)(Ny / nYpix) + 1;
+
+    // create the grid of maps
+    pswarpMapGrid *grid = pswarpMapGridAlloc (nXpts, nYpts);
+
+    // measure the map for the center of each superpixel
+    for (ni = 0, i = xMin; ni < nXpts; i += nXpix, ni++) {
+        for (nj = 0, j = yMin; nj < nYpts; j += nYpix, nj++) {
+            pswarpMapSetLocalModel (grid->maps[ni][nj], dest, src, i, j);
+        }
+    }
+
+    grid->nXpix = nXpix;
+    grid->nYpix = nYpix;
+    grid->xMin = xMin;
+    grid->yMin = yMin;
+    return grid;
+}
+
+/**
+ * set the grid coordinate (gridX,gridY) for the given source image coordinate (ix,iy)
+ * XXX return true if the result is on the src image, false otherwise (???)
+ */
+bool pswarpMapGridSetGrid (pswarpMapGrid *grid, int ix, int iy, int *gridX, int *gridY) {
+
+    *gridX = 0.5 + (ix - grid->xMin) / (double) grid->nXpix;
+    *gridY = 0.5 + (iy - grid->yMin) / (double) grid->nYpix;
+
+    return true;
+}
+
+/**
+ * given the specified grid coordinate (gridX, gridY), return the min and max coordinates for the tile
+ */
+bool pswarpMapGridCoordRange (pswarpMapGrid *grid, int gridX, int gridY, psPlane *min, psPlane *max) {
+
+    min->x = (gridX - 0.5)*grid->nXpix + grid->xMin;
+    min->y = (gridY - 0.5)*grid->nYpix + grid->yMin;
+
+    max->x = min->x + grid->nXpix;
+    max->y = min->y + grid->nYpix;
+
+    return true;
+}
+
+/**
+ * given the specified grid coordinate (gridX), return the x-coordinate for the source image
+ * corresponding to the next grid cell
+ */
+int pswarpMapGridNextGrid_X (pswarpMapGrid *grid, int gridX) {
+
+    int nextX = (gridX + 0.5)*grid->nXpix + grid->xMin;
+    return nextX;
+}
+
+/**
+ * given the specified grid coordinate (gridY), return the y-coordinate for the source image
+ * corresponding to the next grid cell
+ */
+int pswarpMapGridNextGrid_Y (pswarpMapGrid *grid, int gridY) {
+
+    int nextY = (gridY + 0.5)*grid->nYpix + grid->yMin;
+    return nextY;
+}
+
+/**
+ * measure the max error accumulated in applying one grid point to its neighbors
+ * XXX double-check this
+ */
+double pswarpMapGridMaxError (pswarpMapGrid *grid) {
+
+    double xRaw, yRaw;
+    double xRef, yRef;
+    double maxError = 0;
+
+    for (int i = 0; i < grid->nXpts - 1; i++) {
+        for (int j = 0; j < grid->nYpts - 1; j++) {
+
+            // measure the output coordinates for the next grid position using the current grid map
+            // compare with the coordinates measured using the next grid map
+            pswarpMapApply (&xRaw, &yRaw, grid->maps[i][j], grid->maps[i][j]->xo + grid->nXpix, grid->maps[i][j]->yo);
+            pswarpMapApply (&xRef, &yRef, grid->maps[i+1][j], grid->maps[i][j]->xo + grid->nXpix, grid->maps[i][j]->yo);
+
+            double posError = hypot (xRaw-xRef, yRaw-yRef);
+            maxError = PS_MAX (maxError, posError);
+        }
+    }
+    return maxError;
+}
+
+/**
+ * given the source coordinate (inX,inY), return the destination coordinate (outX,outY)
+ */
+bool pswarpMapApply (double *outX, double *outY, pswarpMap *map, double inX, double inY) {
+
+    *outX = map->Xo + map->Xx*inX + map->Xy*inY;
+    *outY = map->Yo + map->Yx*inX + map->Yy*inY;
+
+    return true;
+}
+
+/**
+ * determine the (linear) map for the given pixel (ix,iy) from source image (src) to the destination image (dest)
+ * pixel is in src coords. input and output pixel coordinates are in the parent frame of the image (Note that the
+ * astrometric transformations are supplied for the parent image coordinate frame.
+ */
+bool pswarpMapSetLocalModel (pswarpMap *map, pmReadout *dest, pmReadout *src, int ix, int iy) {
+
+    pmCell *cell = NULL;
+
+    cell = src->parent;
+    pmChip *chipSrc = cell->parent;
+    pmFPA *fpaSrc = chipSrc->parent;
+
+    cell = dest->parent;
+    pmChip *chipDest = cell->parent;
+    pmFPA *fpaDest = chipDest->parent;
+
+    // XXX save these as static for speed?
+    psPlane *offset = psPlaneAlloc();
+
+    psPlane *FP = psPlaneAlloc();
+    psPlane *TP = psPlaneAlloc();
+    psSphere *sky = psSphereAlloc();
+
+    psPlane *V00 = psPlaneAlloc();
+    psPlane *V10 = psPlaneAlloc();
+    psPlane *V01 = psPlaneAlloc();
+
+    // XXX need to include readout->cell->chip offsets
+
+    /** V(0,0) position */
+    offset->x = ix;
+    offset->y = iy;
+    psPlaneTransformApply(FP, chipSrc->toFPA, offset);
+    psPlaneTransformApply (TP, fpaSrc->toTPA, FP);
+    psDeproject (sky, TP, fpaSrc->toSky);
+    psProject (TP, sky, fpaDest->toSky);
+    psPlaneTransformApply (FP, fpaDest->fromTPA, TP);
+    psPlaneTransformApply (V00, chipDest->fromFPA, FP);
+
+    /** V(1,0) position */
+    offset->x = ix + 1;
+    offset->y = iy;
+    psPlaneTransformApply(FP, chipSrc->toFPA, offset);
+    psPlaneTransformApply (TP, fpaSrc->toTPA, FP);
+    psDeproject (sky, TP, fpaSrc->toSky);
+    psProject (TP, sky, fpaDest->toSky);
+    psPlaneTransformApply (FP, fpaDest->fromTPA, TP);
+    psPlaneTransformApply (V10, chipDest->fromFPA, FP);
+
+    /** V(0,1) position */
+    offset->x = ix;
+    offset->y = iy + 1;
+    psPlaneTransformApply(FP, chipSrc->toFPA, offset);
+    psPlaneTransformApply (TP, fpaSrc->toTPA, FP);
+    psDeproject (sky, TP, fpaSrc->toSky);
+    psProject (TP, sky, fpaDest->toSky);
+    psPlaneTransformApply (FP, fpaDest->fromTPA, TP);
+    psPlaneTransformApply (V01, chipDest->fromFPA, FP);
+
+    map->Xx = V10->x - V00->x;
+    map->Xy = V01->x - V00->x;
+    map->Xo = V00->x - map->Xx*ix - map->Xy*iy;
+
+    map->Yx = V10->y - V00->y;
+    map->Yy = V01->y - V00->y;
+    map->Yo = V00->y - map->Yx*ix - map->Yy*iy;
+
+    map->xo = ix;
+    map->yo = iy;
+
+    psFree (offset);
+    psFree (FP);
+    psFree (TP);
+    psFree (sky);
+
+    psFree (V00);
+    psFree (V10);
+    psFree (V01);
+
+    return true;
+}
+
+static void pswarpMapFree (pswarpMap *map) {
+  return;
+}
+
+pswarpMap *pswarpMapAlloc(void) {
+
+  pswarpMap *map = (pswarpMap *) psAlloc (sizeof(pswarpMap));
+  psMemSetDeallocator(map, (psFreeFunc) pswarpMapFree);
+
+  return map;
+}
+
+static void pswarpMapGridFree (pswarpMapGrid *grid) {
+
+    if (grid == NULL) return;
+    if (grid->maps == NULL) return;
+
+    for (int i = 0; i < grid->nXpts; i++) {
+        for (int j = 0; j < grid->nYpts; j++) {
+            psFree (grid->maps[i][j]);
+        }
+        psFree (grid->maps[i]);
+    }
+    psFree (grid->maps);
+    return;
+}
+
+pswarpMapGrid *pswarpMapGridAlloc (int nXpts, int nYpts) {
+
+  pswarpMapGrid *grid = (pswarpMapGrid *) psAlloc (sizeof(pswarpMapGrid));
+  psMemSetDeallocator(grid, (psFreeFunc) pswarpMapGridFree);
+
+  grid->maps = psAlloc (nXpts*sizeof(void **));
+  for (int i = 0; i < nXpts; i++) {
+      grid->maps[i] = psAlloc (nYpts*sizeof(void *));
+      for (int j = 0; j < nYpts; j++) {
+          grid->maps[i][j] = pswarpMapAlloc();
+      }
+  }
+  grid->nXpts = nXpts;
+  grid->nYpts = nYpts;
+
+  grid->nXpix = 0;
+  grid->nYpix = 0;
+
+  return grid;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpMaskStats.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpMaskStats.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpMaskStats.c	(revision 41091)
@@ -0,0 +1,42 @@
+#include "pswarp.h"
+
+bool pswarpMaskStats(const pmReadout *readout, psMetadata *stats, const pmConfig *config)
+{
+  PS_ASSERT_PTR_NON_NULL(readout, false);
+  PS_ASSERT_PTR_NON_NULL(config, false);
+
+  if (!stats || !readout || !readout->data_exists) {
+    // Nothing to process
+    return(true);
+  }
+
+  bool status;
+  psMetadata *recipe = psMetadataLookupMetadata(&status, config->recipes, PSWARP_RECIPE);
+  psU16 staticMaskVal = psMetadataLookupU32(&status, recipe, "MASKSTAT.STATIC");
+  psU16 magicMaskVal = psMetadataLookupU32(&status, recipe, "MASKSTAT.MAGIC");
+  psU16 dynamicMaskVal = psMetadataLookupU32(&status, recipe, "MASKSTAT.DYNAMIC");
+  psU16 advisoryMaskVal = psMetadataLookupU32(&status, recipe, "MASKSTAT.ADVISORY");
+
+  psS32 Npix_valid = 0;
+  psS32 Npix_static = 0;
+  psS32 Npix_magic = 0;
+  psS32 Npix_dynamic = 0;
+  psS32 Npix_advisory = 0;
+
+  psImage *mask = readout->mask;  // Mask of interest;
+  if (!pmSingleImageMaskStats(mask,&Npix_valid,&Npix_static,&Npix_magic,
+                              &Npix_dynamic,&Npix_advisory,
+                              staticMaskVal,magicMaskVal,
+                              dynamicMaskVal,advisoryMaskVal)) {
+    psError(PS_ERR_UNKNOWN, false, "Unable to calculate masks for readout.");
+    return false;
+  }
+
+  // XXX with multiple inputs (eg, output stacks -> exposure), these only represent the last input
+  psMetadataAddS32(stats, PS_LIST_TAIL,"MASKFRAC_NPIX",     PS_META_REPLACE, "Number of valid pixels", Npix_valid);
+  psMetadataAddF32(stats,PS_LIST_TAIL, "MASKFRAC_STATIC",   PS_META_REPLACE, "Fraction of pixels statically masked", (float) Npix_static / Npix_valid);
+  psMetadataAddF32(stats,PS_LIST_TAIL, "MASKFRAC_DYNAMIC",  PS_META_REPLACE, "Fraction of pixels dynamically masked", (float) Npix_dynamic / Npix_valid);
+  psMetadataAddF32(stats,PS_LIST_TAIL, "MASKFRAC_MAGIC",    PS_META_REPLACE, "Fraction of pixels magically masked", (float) Npix_magic / Npix_valid);
+  psMetadataAddF32(stats,PS_LIST_TAIL, "MASKFRAC_ADVISORY", PS_META_REPLACE, "Fraction of pixels masked as an advisory", (float) Npix_advisory / Npix_valid);
+  return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpMatchRange.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpMatchRange.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpMatchRange.c	(revision 41091)
@@ -0,0 +1,132 @@
+/** @file pswarpMatchRange.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-05 20:44:04 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "pswarp.h"
+
+# define TEST_MINMAX \
+    psPlaneTransformApply(destFP, chipDest->toFPA, destPix); \
+    psPlaneTransformApply (destTP, fpaDest->toTPA, destFP); \
+    psDeproject (sky, destTP, fpaDest->toSky); \
+    psProject (srcTP, sky, fpaSrc->toSky); \
+    psPlaneTransformApply (srcFP, fpaSrc->fromTPA, srcTP); \
+    psPlaneTransformApply (srcPix, chipSrc->fromFPA, srcFP); \
+    *minX = PS_MIN (*minX, srcPix->x); \
+    *minY = PS_MIN (*minY, srcPix->y); \
+    *maxX = PS_MAX (*maxX, srcPix->x); \
+    *maxY = PS_MAX (*maxY, srcPix->y);
+
+/** we are warping from src to dest.  find the max overlapping pixels in the INPUT (src)
+ * coordinate frame.  NOTE: these are in the parent pixel frame since the astrometric
+ * transformation refers to the parent frame
+ */
+bool pswarpMatchRange (int *minX, int *minY, int *maxX, int *maxY, pmReadout *dest, pmReadout *src) {
+
+    // transform input corners and edge centers to output coords
+    // find max overlapping region of output image
+    
+    pmCell *cell = NULL;
+
+    cell = src->parent;
+    pmChip *chipSrc = cell->parent;
+    pmFPA *fpaSrc = chipSrc->parent;
+
+    cell = dest->parent;
+    pmChip *chipDest = cell->parent;
+    pmFPA *fpaDest = chipDest->parent;
+
+    *minX = src->image->numCols + src->image->col0 - 1;
+    *minY = src->image->numRows + src->image->row0 - 1;
+    *maxX = src->image->col0;
+    *maxY = src->image->row0;
+
+    // XXX save these as static for speed?
+    psPlane *srcPix = psPlaneAlloc();
+    psPlane *srcFP  = psPlaneAlloc();
+    psPlane *srcTP  = psPlaneAlloc();
+
+    psPlane *destPix = psPlaneAlloc();
+    psPlane *destFP  = psPlaneAlloc();
+    psPlane *destTP  = psPlaneAlloc();
+
+    psSphere *sky = psSphereAlloc();
+
+    destPix->x = dest->image->col0;
+    destPix->y = dest->image->row0;
+    TEST_MINMAX;
+    
+    destPix->x = dest->image->col0 + dest->image->numCols;
+    destPix->y = dest->image->row0;
+    TEST_MINMAX;
+    
+    destPix->x = dest->image->col0 + dest->image->numCols;
+    destPix->y = dest->image->row0 + dest->image->numRows;
+    TEST_MINMAX;
+    
+    destPix->x = dest->image->col0;
+    destPix->y = dest->image->row0 + dest->image->numRows;
+    TEST_MINMAX;
+    
+    destPix->x = dest->image->col0 + 0.5*dest->image->numCols;
+    destPix->y = dest->image->row0;
+    TEST_MINMAX;
+    
+    destPix->x = dest->image->col0;
+    destPix->y = dest->image->row0 + 0.5*dest->image->numRows;
+    TEST_MINMAX;
+    
+    destPix->x = dest->image->col0 + 0.5*dest->image->numCols;
+    destPix->y = dest->image->row0;
+    TEST_MINMAX;
+
+    destPix->x = dest->image->col0;
+    destPix->y = dest->image->row0 + 0.5*dest->image->numRows;
+    TEST_MINMAX;
+
+    *minX = PS_MAX (*minX, src->image->col0);
+    *minY = PS_MAX (*minY, src->image->row0);
+    *maxX = PS_MIN (*maxX, src->image->numCols + src->image->col0);
+    *maxY = PS_MIN (*maxY, src->image->numRows + src->image->row0);
+
+    // demo forward and backward transformation
+# if (0)
+    srcPix->x = *minX;
+    srcPix->y = *minY;
+    psPlaneTransformApply(srcFP, chipSrc->toFPA, srcPix); 
+    psPlaneTransformApply (srcTP, fpaSrc->toTPA, srcFP); 
+    psDeproject (sky, srcTP, fpaSrc->toSky); 
+    psProject (destTP, sky, fpaDest->toSky); 
+    psPlaneTransformApply (destFP, fpaDest->fromTPA, destTP); 
+    psPlaneTransformApply (destPix, chipDest->fromFPA, destFP); 
+    fprintf (stderr, "%f,%f -> %f,%f ", srcPix->x, srcPix->y, destPix->x, destPix->y);
+
+    psPlaneTransformApply(destFP, chipDest->toFPA, destPix); 
+    psPlaneTransformApply (destTP, fpaDest->toTPA, destFP); 
+    psDeproject (sky, destTP, fpaDest->toSky); 
+    psProject (srcTP, sky, fpaSrc->toSky); 
+    psPlaneTransformApply (srcFP, fpaSrc->fromTPA, srcTP); 
+    psPlaneTransformApply (srcPix, chipSrc->fromFPA, srcFP); 
+    fprintf (stderr, "-> %f,%f\n", srcPix->x, srcPix->y);
+# endif
+
+    psFree (srcPix);
+    psFree (srcFP);
+    psFree (srcTP);
+
+    psFree (destPix);
+    psFree (destFP);
+    psFree (destTP);
+
+    psFree (sky);
+
+    return true;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpOptions.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpOptions.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpOptions.c	(revision 41091)
@@ -0,0 +1,129 @@
+/** @file pswarpOptionss.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.24 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-06 03:10:36 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "pswarp.h"
+
+/**
+ * Parse the recipe and format into the arguments
+ */
+bool pswarpOptions(pmConfig *config)
+{
+    // Select the appropriate recipe
+    psMetadata *recipe  = psMetadataLookupPtr(NULL, config->recipes, PSWARP_RECIPE);
+    if (!recipe) {
+        psError(PSWARP_ERR_CONFIG, true, "Can't find %s recipe!\n", PSWARP_RECIPE);
+        return false;
+    }
+
+    // Get grid size
+    bool status;                        ///< Status of MD lookup
+    int nGridX = psMetadataLookupS32(&status, recipe, "GRID.NX");
+    if (!status || nGridX <= 0) {
+        nGridX = 128;
+        psWarning("GRID.NX is not set in the recipe --- defaulting to %d", nGridX);
+    }
+    int nGridY = psMetadataLookupS32(&status, recipe, "GRID.NY");
+    if (!status) {
+        nGridY = 128;
+        psWarning("GRID.NY is not set in the recipe --- defaulting to %d", nGridY);
+    }
+
+    // Get interpolation mode
+    const char *name = psMetadataLookupStr (&status, recipe, "INTERPOLATION.MODE"); ///< Name of interp mode
+    if (!name) {
+        name = "BILINEAR";
+        psLogMsg("pswarp", 3, "defaulting to %s interpolation", name);
+    }
+    psImageInterpolateMode interpolationMode = psImageInterpolateModeFromString(name); ///< Mode for interp.
+    if (interpolationMode == PS_INTERPOLATE_NONE) {
+        interpolationMode = PS_INTERPOLATE_BILINEAR;
+        psLogMsg ("pswarp", 3,
+                  "Unknown interpolation mode %s, defaulting to bilinear interpolation\n", name);
+        name = "BILINEAR";
+    }
+
+    int numKernels = psMetadataLookupS32(&status, recipe, "INTERPOLATION.NUM");
+    if (!status) {
+        numKernels = 0;
+        psWarning("INTERPOLATION.NUM is not set in the recipe --- defaulting to %d", numKernels);
+    }
+
+    float poorFrac = psMetadataLookupF32(&status, recipe, "POOR.FRAC"); ///< Frac of bad flux for a "poor"
+    if (!status) {
+        poorFrac = 0.0;
+        psWarning("POOR.FRAC is not set in the %s recipe --- defaulting to %f.", PSWARP_RECIPE, poorFrac);
+    }
+
+    bool PSF = psMetadataLookupBool(&status, recipe, "PSF"); ///< Generate a PSF model?
+    if (!status) {
+        PSF = true;
+        psWarning("PSF is not set in the %s recipe --- defaulting to TRUE.", PSWARP_RECIPE);
+    }
+
+    // BACKGROUND.MODEL gets set in config->arguments (to false) if no input model is found
+    bool doBKG = psMetadataLookupBool(&status,config->arguments, "BACKGROUND.MODEL"); ///< Generate the warped background model?
+    if (!status) {
+      // if it is not in config->arguments, look in recipe
+      doBKG = psMetadataLookupBool(&status,recipe, "BACKGROUND.MODEL"); ///< Generate the warped background model?
+      if (!status) {
+	doBKG = false;
+      psWarning("BACKGROUND.MODEL is not set in the %s recipe -- defaulting to FALSE.", PSWARP_RECIPE);
+      }
+    }
+
+    int bkgXgrid = psMetadataLookupS32(&status,recipe, "BKG.XGRID"); ///< Xsize of background model
+    if (!status) {
+      bkgXgrid = 10;
+      psWarning("BKG.XGRID is not set in the %s recipe -- defaultint to %d.",PSWARP_RECIPE,bkgXgrid);
+    }
+    int bkgYgrid = psMetadataLookupS32(&status,recipe, "BKG.YGRID"); ///< Xsize of background model
+    if (!status) {
+      bkgYgrid = 10;
+      psWarning("BKG.YGRID is not set in the %s recipe -- defaultint to %d.",PSWARP_RECIPE,bkgYgrid);
+    }
+
+    // See if we should use a more accurate WCS model.
+    int config_additional_orders = psMetadataLookupS32(&status,recipe, "ADDITIONAL_WCS_ORDERS");
+    if (!status) {
+      // We did not find this recipe option.  We are likely updating an old config.
+      config_additional_orders = 0;
+    }
+    
+
+    
+    // Set recipe values in the recipe (since we've possibly altered some)
+    psMetadataAddS32(recipe, PS_LIST_TAIL, "GRID.NX", PS_META_REPLACE, "Iso-astrom grid spacing in x", nGridX);
+    psMetadataAddS32(recipe, PS_LIST_TAIL, "GRID.NY", PS_META_REPLACE, "Iso-astrom grid spacing in y", nGridY);
+    psMetadataAddStr(recipe, PS_LIST_TAIL, "INTERPOLATION.MODE", PS_META_REPLACE, "Interpolation mode", name);
+    psMetadataAddS32(recipe, PS_LIST_TAIL, "INTERPOLATION.NUM", PS_META_REPLACE, "Interpolation pre-calculated kernels", numKernels);
+    psMetadataAddF32(recipe, PS_LIST_TAIL, "POOR.FRAC", PS_META_REPLACE, "Fraction of bad flux for a pixel to be marked as poor", poorFrac);
+    psMetadataAddBool(recipe, PS_LIST_TAIL, "PSF", PS_META_REPLACE, "Generate a PSF Model?", PSF);
+    psMetadataAddBool(recipe, PS_LIST_TAIL, "BACKGROUND.MODEL", PS_META_REPLACE, "Generate the warped background model?", doBKG);
+    psMetadataAddS32(recipe, PS_LIST_TAIL, "BKG.XGRID", PS_META_REPLACE, "Xsize of background model", bkgXgrid);
+    psMetadataAddS32(recipe, PS_LIST_TAIL, "BKG.YGRID", PS_META_REPLACE, "Ysize of background model", bkgYgrid);
+    
+    // Set recipe values in the arguments
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "GRID.NX", 0, "Iso-astrom grid spacing in x", nGridX);
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "GRID.NY", 0, "Iso-astrom grid spacing in y", nGridY);
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "INTERPOLATION.MODE", 0, "Interpolation mode", interpolationMode);
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "INTERPOLATION.NUM", 0, "Interpolation pre-calculated kernels", numKernels);
+    psMetadataAddF32(config->arguments, PS_LIST_TAIL, "POOR.FRAC", 0, "Fraction of bad flux for a pixel to be marked as poor", poorFrac);
+    psMetadataAddBool(config->arguments, PS_LIST_TAIL, "PSF", PS_META_REPLACE, "Generate a PSF Model?", PSF);
+    psMetadataAddBool(config->arguments, PS_LIST_TAIL, "BACKGROUND.MODEL", PS_META_REPLACE, "Generate the warped background model?", doBKG);
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "BKG.XGRID", PS_META_REPLACE, "Xsize of background model", bkgXgrid);
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "BKG.YGRID", PS_META_REPLACE, "Ysize of background model", bkgYgrid);
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "ADDITIONAL_WCS_ORDERS", PS_META_REPLACE, "Additional orders for bilevel fit.", config_additional_orders);
+
+    psTrace("pswarp", 1, "Done with pswarpOptions...\n");
+
+    return (config);
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpOverlaps.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpOverlaps.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpOverlaps.c	(revision 41091)
@@ -0,0 +1,253 @@
+# include "pswarp.h"
+
+static void pswarpBoundsFree (pswarpBounds *bounds) {
+    psFree (bounds->Pmin);
+    psFree (bounds->Pmax);
+    psFree (bounds->Qmin);
+    psFree (bounds->Qmax);
+}
+
+pswarpBounds *pswarpBoundsAlloc() {
+
+    pswarpBounds *bounds = psAlloc(sizeof(pswarpBounds));
+    psMemSetDeallocator(bounds, (psFreeFunc)pswarpBoundsFree);
+
+    bounds->Pmin = psVectorAllocEmpty(32, PS_DATA_F32);
+    bounds->Pmax = psVectorAllocEmpty(32, PS_DATA_F32);
+    bounds->Qmin = psVectorAllocEmpty(32, PS_DATA_F32);
+    bounds->Qmax = psVectorAllocEmpty(32, PS_DATA_F32);
+    return bounds;
+}
+
+bool pswarpBoundsAppend(pswarpBounds *bounds, float Pmin, float Pmax, float Qmin, float Qmax) {
+    psVectorAppend (bounds->Pmin, Pmin);
+    psVectorAppend (bounds->Pmax, Pmax);
+    psVectorAppend (bounds->Qmin, Qmin);
+    psVectorAppend (bounds->Qmax, Qmax);
+    return true;
+}
+
+pswarpBounds *pswarpMakeBounds (pmFPA *fpa, psProjection *frame) {
+
+    pswarpBounds *bounds = pswarpBoundsAlloc ();
+    
+    psPlane *CH = psPlaneAlloc();
+    psPlane *FP  = psPlaneAlloc();
+    psPlane *TP  = psPlaneAlloc();
+    psPlane *FR  = psPlaneAlloc();
+    psSphere *sky = psSphereAlloc();
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    pmChip *chip = NULL;
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+	psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+	if (!chip->process || !chip->file_exists) { 
+	    // this ensures one entry per chip (regardless of existence)
+	    pswarpBoundsAppend (bounds, NAN, NAN, NAN, NAN);
+	    continue; 
+	}
+
+	// we've got the output astrom header
+	pmHDU *hdu = pmHDUFromChip(chip); ///< HDU for source
+	assert (hdu);
+	assert (hdu->header);
+	int numCols = psMetadataLookupS32(NULL, hdu->header, "NAXIS1"); ///< Number of columns
+	int numRows = psMetadataLookupS32(NULL, hdu->header, "NAXIS2"); ///< Number of rows
+	if ((numCols == 0) || (numRows == 0)) {
+	    psError(PSWARP_ERR_DATA, false, "astrom has invalid dimensions %d x %d", numCols, numRows);
+	    psFree(view);
+	    return false;
+	}
+
+	float Pmin = +1e9;
+	float Pmax = -1e9;
+	float Qmin = +1e9;
+	float Qmax = -1e9;
+
+	// find P,Q at corners and center of edges (and center just for ease of code)
+	for (float ix = 0.0; ix <= 1.0; ix += 0.5) {
+	    for (float iy = 0.0; iy <= 1.0; iy += 0.5) {
+
+		CH->x = ix * numCols;
+		CH->y = iy * numRows;
+		psPlaneTransformApply(FP, chip->toFPA, CH); 
+		psPlaneTransformApply(TP, fpa->toTPA, FP); 
+		psDeproject (sky, TP, fpa->toSky); 
+		psProject (FR, sky, frame); 
+
+# if (0)
+		fprintf (stderr, "%10.3f %10.3f | %10.3f %10.3f | %10.3f %10.3f | %10.6f %10.6f | %10.3f %10.3f\n", 
+			 CH->x, CH->y, 
+			 FP->x, FP->y, 
+			 TP->x, TP->y, 
+			 PS_DEG_RAD*sky->r, PS_DEG_RAD*sky->d, 
+			 FR->x, FR->y);
+# endif
+
+		Pmin = PS_MIN(Pmin, FR->x);
+		Pmax = PS_MAX(Pmax, FR->x);
+		Qmin = PS_MIN(Qmin, FR->y);
+		Qmax = PS_MAX(Qmax, FR->y);
+	    }
+	}
+	pswarpBoundsAppend (bounds, Pmin, Pmax, Qmin, Qmax);
+    }
+    psFree(view);
+    psFree(CH);
+    psFree(FP);
+    psFree(TP);
+    psFree(FR);
+    psFree(sky);
+
+    return bounds;
+}
+
+// find R,D for the center of the output pmFPA
+psProjection *pswarpLocalFrame (pmFPA *fpa) {
+
+    // find R,D for each corner of each chip and take the average
+    psVector *Rvec = psVectorAllocEmpty (256, PS_DATA_F32);
+    psVector *Dvec = psVectorAllocEmpty (256, PS_DATA_F32);
+    
+    psPlane *CH = psPlaneAlloc();
+    psPlane *FP  = psPlaneAlloc();
+    psPlane *TP  = psPlaneAlloc();
+    psSphere *sky = psSphereAlloc();
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // we are looping over the chips of the OUTPUT fpa.  chip->file_exists is false for all chips. 
+    // at this point, I cannot exclude any of these chips (I do not know which output chips will not be created).
+    // NOTE : I either have to use SKYCELL fpa and ensure SKYCELL->chip->toFPA exists or grab hdu from SKYCELL
+
+    pmChip *chip = NULL;
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+	psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+	// if (!chip->file_exists) { continue; } // this fpa refers to the output file, so no chip exist
+	if (!chip->toFPA) { continue; } // this fpa refers to the output file, so no chip exist
+	assert (chip->toFPA);
+	chip->process = true; // start with process true, we will de-activate later (pswarpFindOverlaps)
+
+	// we've got the output astrom header
+	pmHDU *hdu = pmHDUFromChip(chip); ///< HDU for source
+	assert (hdu);
+	assert (hdu->header);
+	int numCols = psMetadataLookupS32(NULL, hdu->header, "NAXIS1"); ///< Number of columns
+	int numRows = psMetadataLookupS32(NULL, hdu->header, "NAXIS2"); ///< Number of rows
+	if ((numCols == 0) || (numRows == 0)) {
+	    psError(PSWARP_ERR_DATA, false, "astrom has invalid dimensions %d x %d", numCols, numRows);
+	    psFree(view);
+	    return false;
+	}
+
+	// get R,D for all corners
+	for (float ix = 0.0; ix <= 1.0; ix += 1.0) {
+	    for (float iy = 0.0; iy <= 1.0; iy += 1.0) {
+		CH->x = ix * numCols;
+		CH->y = iy * numRows;
+		psPlaneTransformApply(FP, chip->toFPA, CH); 
+		psPlaneTransformApply(TP, fpa->toTPA, FP); 
+		psDeproject (sky, TP, fpa->toSky); 
+		psVectorAppend (Rvec, sky->r);
+		psVectorAppend (Dvec, sky->d);
+	    }
+	}
+    }
+    psFree(view);
+    psFree(CH);
+    psFree(FP);
+    psFree(TP);
+    psFree(sky);
+
+    psStats *stats = psStatsAlloc(PS_STAT_SAMPLE_MEAN);
+    psVectorStats (stats, Rvec, NULL, NULL, 0);
+    double Rmid = stats->sampleMean;
+
+    psStatsInit (stats);
+    psVectorStats (stats, Dvec, NULL, NULL, 0);
+    double Dmid = stats->sampleMean;
+
+    psProjection *frame = psProjectionAlloc (Rmid, Dmid, PS_RAD_DEG/3600.0, PS_RAD_DEG/3600.0, PS_PROJ_TAN);
+    return frame;
+}
+
+bool pswarpFindOverlap (pmFPA *input, pmFPA *output, pswarpBounds *src, pswarpBounds *tgt) {
+
+    bool status = false;
+
+    // we have the source and target bounds.  loop over all output elements and check if any of the source
+    // elements overlap it.
+
+    // also add the list if input (src) cells which land on each output (tgt) chip we do this
+    // by only identifying the input and output chips and adding all input chip cells to each
+    // output cell
+
+    assert (output->chips->n == tgt->Pmin->n);
+    assert (input->chips->n == src->Pmin->n);
+
+    for (int j = 0; j < output->chips->n; j++) {
+	pmChip *chip = output->chips->data[j];
+
+	if (!isfinite(tgt->Pmin->data.F32[j])) continue;
+	if (!isfinite(tgt->Pmax->data.F32[j])) continue;
+	if (!isfinite(tgt->Qmin->data.F32[j])) continue;
+	if (!isfinite(tgt->Qmax->data.F32[j])) continue;
+
+	// we have src bounds
+	float Pmin = tgt->Pmin->data.F32[j];
+	float Pmax = tgt->Pmax->data.F32[j];
+	float Qmin = tgt->Qmin->data.F32[j];
+	float Qmax = tgt->Qmax->data.F32[j];
+
+	psArray *inputChips = psArrayAllocEmpty(8);
+
+	bool hasOverlap = false;
+	for (int i = 0; i < src->Pmin->n; i++) {
+	    // overlaps in P?
+	    if (!isfinite(src->Pmin->data.F32[i])) continue;
+	    if (!isfinite(src->Pmax->data.F32[i])) continue;
+	    if (!isfinite(src->Qmin->data.F32[i])) continue;
+	    if (!isfinite(src->Qmax->data.F32[i])) continue;
+
+	    if (Pmin > src->Pmax->data.F32[i]) continue;
+	    if (Pmax < src->Pmin->data.F32[i]) continue;
+
+	    if (Qmin > src->Qmax->data.F32[i]) continue;
+	    if (Qmax < src->Qmin->data.F32[i]) continue;
+
+	    hasOverlap = true;
+	    psArrayAdd (inputChips, 1, input->chips->data[i]);
+	}
+	chip->process |= hasOverlap;
+	if (hasOverlap) {
+	    pmChipSelectCells (chip);
+	    // get the complete list of input cells for this set of input chips
+	    // pmConceptsAverageCells needs a psList (not a psArray)
+	    psArray *inputCells = psArrayAllocEmpty(8);
+	    for (int nChip = 0; nChip < inputChips->n; nChip++) {
+		pmChip *inputChip = inputChips->data[nChip];
+		for (int nCell = 0; nCell < inputChip->cells->n; nCell++) {
+		    psArrayAdd (inputCells, 8, inputChip->cells->data[nCell]);
+		}
+	    }
+	    
+	    // place the inputCells on each of the output cells 
+	    for (int nCell = 0; nCell < chip->cells->n; nCell++) {
+		pmCell *outputCell = chip->cells->data[nCell];
+		psList *outList = psMetadataLookupPtr(&status, outputCell->analysis, "INPUT.CELLS");
+		if (!outList) {
+		    outList = psListAlloc(NULL);
+		    psMetadataAddPtr(outputCell->analysis, PS_LIST_TAIL, "INPUT.CELLS", PS_DATA_LIST , "input cells touching this output cell", outList);
+		    psFree (outList);
+		}
+		for (int nCell = 0; nCell < inputCells->n; nCell ++) {
+		    psListAdd (outList, PS_LIST_TAIL, inputCells->data[nCell]);
+		}
+	    }
+	    psFree (inputCells);
+	}
+	psFree (inputChips);
+    }
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpParseCamera.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpParseCamera.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpParseCamera.c	(revision 41091)
@@ -0,0 +1,395 @@
+/** @file pswarpParseCamera.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.24 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-06 03:10:36 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "pswarp.h"
+
+static bool foundAstrom = false;
+static bool foundVariance = false;
+static bool foundBackground = false;
+
+bool pswarpParseCamera(pmConfig *config)
+{
+    psAssert(config, "Require configuration");
+    bool status;                          // Status of MD lookup
+
+    // *** parse the input information (either from -file or from -input)
+
+    // if INPUTS exists, we have a metadata file to provide input, variance, mask, etc
+    psMetadata *inputFile = psMetadataLookupPtr(&status, config->arguments, "INPUTS");
+    if (inputFile) {
+	if (!pswarpParseMultiInput (config, inputFile)) {
+	    psError(PSWARP_ERR_CONFIG, false, "failed to parse multi-input file");
+	    return false;
+	}
+    } else {
+	if (!pswarpParseSingleInput (config)) {
+	    psError(PSWARP_ERR_CONFIG, false, "failed to parse multi-input file");
+	    return false;
+	}
+    }
+
+    // once we have read the input mask headers (if any), we can use that to set the mask bits
+    if (!pswarpSetMaskBits(config)) {
+        psError(psErrorCodeLast(), false, "failed to set mask bits");
+        return false;
+    }
+
+    // *** parse the output information (output target, output astrometry [skycell])
+
+    // The input skycell is a required argument: it defines the output image
+    pmConfig *skyConfig = NULL;
+    pmFPAfile *skycell = NULL;
+
+    skycell = pmFPAfileDefineNewConfig(&status, &skyConfig, config, "PSWARP.SKYCELL", "SKYCELL");
+    if (!status || !skycell) {
+      psError(psErrorCodeLast(), false, "Failed to build FPA from PSWARP.SKYCELL");
+      return false;
+      }
+    psMetadataAddStr(config->arguments, PS_LIST_TAIL, "SKYCELL.CAMERA", 0, "Name of camera for skycell", skyConfig->cameraName);
+    psFree(skyConfig);
+
+    psMetadata *recipe = psMetadataLookupPtr (&status, config->recipes, PSWARP_RECIPE);
+    if (!recipe) {
+        psError(PSWARP_ERR_CONFIG, false, "missing recipe %s", PSWARP_RECIPE);
+        return false;
+    }
+
+    // use the skycell camera or the input camera?
+    bool useInputCamera = !strcmp (skycell->cameraName, "SIMPLE");
+
+    // The output skycell
+    pmFPAfile *output = useInputCamera ? 
+	pmFPAfileDefineSkycell(config, NULL, "PSWARP.OUTPUT") :
+	pmFPAfileDefineOutputForFormat(config, NULL, "PSWARP.OUTPUT", skycell->cameraName, skycell->formatName);
+    if (!output) {
+        psError(psErrorCodeLast(), false, "Failed to build FPA from PSWARP.OUTPUT");
+        return false;
+    }
+    output->save = true;
+    pmFPAAddSourceFromFormat(output->fpa, output->format); // ** builds the HDUs, is this OK?
+
+    pmFPAfile *outMask = useInputCamera ?
+	pmFPAfileDefineSkycell(config, output->fpa, "PSWARP.OUTPUT.MASK"):
+	pmFPAfileDefineOutputForFormat(config, output->fpa, "PSWARP.OUTPUT.MASK", skycell->cameraName, skycell->formatName);
+    if (!outMask) {
+        psError(psErrorCodeLast(), false, "Failed to build FPA from PSWARP.OUTPUT.MASK");
+        return false;
+    }
+    outMask->save = true;
+
+    // only create an output variance in we supply an input variance
+    if (foundVariance) {
+	pmFPAfile *outVariance = useInputCamera ? 
+	    pmFPAfileDefineSkycell(config, output->fpa, "PSWARP.OUTPUT.VARIANCE"):
+	    pmFPAfileDefineOutputForFormat(config, output->fpa, "PSWARP.OUTPUT.VARIANCE", skycell->cameraName, skycell->formatName);
+        if (!outVariance) {
+            psError(psErrorCodeLast(), false, "Failed to build FPA from PSWARP.OUTPUT.VARIANCE");
+            return false;
+        }
+        outVariance->save = true;
+    }
+
+    // XXX we assume input sources come from the input astrom description, but this need not be true (we could define an input sources file)
+    if (foundAstrom && psMetadataLookupBool(&status, recipe, "SOURCES")) {
+	pmFPAfile *outSources = useInputCamera ? 
+	    pmFPAfileDefineSkycell(config, output->fpa, "PSWARP.OUTPUT.SOURCES"):
+	    pmFPAfileDefineOutputForFormat(config, output->fpa, "PSWARP.OUTPUT.SOURCES", skycell->cameraName, skycell->formatName);
+        if (!outSources) {
+            psError(psErrorCodeLast(), false, "Failed to build FPA from PSWARP.OUTPUT.SOURCES");
+            return false;
+        }
+        outSources->save = true;
+    }
+
+    // only create an output background if an input background is supplied
+    if (foundBackground && psMetadataLookupBool(&status, recipe, "BACKGROUND.MODEL")) {
+	// pmFPAfile *outBackground = pmFPAfileDefineSkycell(config, NULL, "PSWARP.OUTPUT.BKGMODEL");
+	pmFPAfile *outBackground = useInputCamera ? 
+	    pmFPAfileDefineSkycell(config, NULL, "PSWARP.OUTPUT.BKGMODEL"):
+	    pmFPAfileDefineOutputForFormat(config, NULL, "PSWARP.OUTPUT.BKGMODEL", skycell->cameraName, skycell->formatName);
+	if (!outBackground) {
+	    psError(psErrorCodeLast(), false, "Failed to build FPA from PSWARP.OUTPUT.BKGMODEL");
+	    return false;
+	}
+	outBackground->xBin = psMetadataLookupS32(&status, recipe, "BKG.XGRID");
+	outBackground->yBin = psMetadataLookupS32(&status, recipe, "BKG.YGRID");
+	outBackground->save = true;
+	pmFPAAddSourceFromFormat(outBackground->fpa, outBackground->format); // ** builds the HDUs, is this OK?
+    }
+
+    if (psMetadataLookupBool(&status, recipe, "PSF")) {
+        // This file, PSPHOT.INPUT, is just used as a carrier; output files (eg, PSPHOT.RESID) are defined by psphotDefineFiles
+	pmFPAfile *psphotInput = useInputCamera ? 
+	    pmFPAfileDefineSkycell(config, NULL, "PSPHOT.INPUT"):
+	    pmFPAfileDefineOutputForFormat(config, NULL, "PSPHOT.INPUT", skycell->cameraName, skycell->formatName);
+        if (!psphotInput) {
+            psError(psErrorCodeLast(), false, _("Unable to generate output file from PSPHOT.INPUT"));
+            return false;
+        }
+        psphotInput->src = psMemIncrRefCounter(output->fpa);
+	pmFPAAddSourceFromFormat(psphotInput->fpa, psphotInput->format); // ** builds the HDUs, is this OK?
+
+        // specify the number of psphot input images (psphotReadout loops over all input images)
+        psMetadataAddS32 (config->arguments, PS_LIST_TAIL, "PSPHOT.INPUT.NUM", PS_META_REPLACE, "number of inputs", 1);
+
+	// the input sources (read from the input astrometry file) are transformed (in pswarpLoop) to the readout->analysis
+	// entries of the output file PSWARP.OUTPUT.SOURCES, associated with the main output pmFPAfile PSWARP.OUTPUT
+
+	// the PSPHOT.INPUT.CMF is used to supply those sources to psphot (specifically psphotLoadPSFSources).  
+
+        // pmFPAfile *psphotInSources = pmFPAfileDefineSkycell(config, output->fpa, "PSPHOT.INPUT.CMF");
+	pmFPAfile *psphotInSources = useInputCamera ? 
+	    pmFPAfileDefineSkycell(config, output->fpa, "PSPHOT.INPUT.CMF"):
+	    pmFPAfileDefineOutputForFormat(config, output->fpa, "PSPHOT.INPUT.CMF", skycell->cameraName, skycell->formatName);
+        if (!psphotInSources) {
+            psError(psErrorCodeLast(), false, _("Unable to generate output file from PSPHOT.INPUT.CMF"));
+            return false;
+        }
+
+        // Ensure psphot defines an output file for the PSF.
+        psMetadata *psphotRecipe = psMetadataLookupPtr(NULL, config->recipes, PSPHOT_RECIPE); // Recipe
+        if (!psphotRecipe) {
+            psError(PSWARP_ERR_CONFIG, false, "Unable to find %s recipe.", PSPHOT_RECIPE);
+            return false;
+        }
+        psMetadataAddBool(psphotRecipe, PS_LIST_TAIL, "SAVE.PSF", PS_META_REPLACE, "Save PSF?", true);
+
+        // Define associated psphot input/output files
+        if (!psphotDefineFiles(config, psphotInput)) {
+            psError(PSWARP_ERR_CONFIG, false,
+                    "Unable to define the additional input/output files for psphot");
+            return false;
+        }
+
+        // Turn off writing the sources we find as part of deriving the PSF --- they might clobber the ones we
+        // get from transforming the input coordinates.
+        pmFPAfile *psphotSources = psMetadataLookupPtr(NULL, config->files, "PSPHOT.OUTPUT");
+        assert(psphotSources);
+        psphotSources->save = false;
+    }
+ 
+    // only keep the necessary (output) camera configs and relevant recipes
+    const char *skyCamera = psMetadataLookupStr(NULL, config->arguments, "SKYCELL.CAMERA");
+    pmConfigCamerasCull(config, skyCamera);
+    pmConfigRecipesCull(config, "PSWARP,PPSTATS,PSPHOT,PSASTRO,MASKS,JPEG");
+
+    psTrace("pswarp", 1, "Done with pswarpParseCamera...\n");
+    return true;
+}
+
+bool pswarpParseSingleInput (pmConfig *config) {
+
+    // The input image(s) is required: it defines the camera
+    pmFPAfile *input = pswarpDefineInputFile(config, NULL, "PSWARP.INPUT", "INPUT", PM_FPA_FILE_IMAGE);
+    if (!input) {
+        psError(psErrorCodeLast(), false, "Failed to build FPA from PSWARP.INPUT");
+        return false;
+    }
+
+    // Define the input astrometry file(s) (may be either WCS or CMF)
+    bool status = false;
+    pmFPAfile *astrom = pmFPAfileDefineFromArgs(&status, config, "PSWARP.ASTROM", "ASTROM");
+    if (!astrom) {
+        // look for the file on the RUN metadata
+        astrom = pmFPAfileDefineFromRun(&status, NULL, config, "PSWARP.ASTROM"); // File to return
+        if (!status) {
+            psLogMsg("pswarp", PS_LOG_INFO, "Failed to load file definition for %s", "PSWARP.ASTROM");
+        }
+    }
+    if (astrom) {
+	if ((astrom->type != PM_FPA_FILE_CMF) && (astrom->type != PM_FPA_FILE_WCS)) {
+	    psLogMsg("pswarp", PS_LOG_INFO, "%s is neither CMF nor WCS", "PSWARP.ASTROM");
+	    // XXX raise an error here??
+	} else {
+	    foundAstrom = true;
+	}
+    }
+    psLogMsg("pswarp", PS_LOG_INFO, "Astrometry source: %s", astrom ? "supplied" : "header");
+
+    // Define the input mask file(s)
+    pmFPAfile *inMask = pswarpDefineInputFile(config, input, "PSWARP.MASK", "MASK", PM_FPA_FILE_MASK);
+    if (!inMask) {
+        psLogMsg("pswarp", PS_LOG_INFO, "No mask supplied");
+    }
+
+    // Define the input variance file(s)
+    pmFPAfile *inVariance = pswarpDefineInputFile(config, input, "PSWARP.VARIANCE", "VARIANCE", PM_FPA_FILE_VARIANCE);
+    if (inVariance) {
+	foundVariance = true;
+    } else {
+        psLogMsg("pswarp", PS_LOG_INFO, "No variance supplied");
+    }
+
+    pmFPAfile *inBackground = pswarpDefineInputFile(config, NULL, "PSWARP.BKGMODEL", "BACKGROUND", PM_FPA_FILE_IMAGE);
+    if (inBackground) {
+	foundBackground = true;
+    } else {
+      // cannot do the background model if an input is not supplied.
+      psMetadataAddBool (config->arguments, PS_LIST_TAIL, "BACKGROUND.MODEL", PS_META_REPLACE, "no input background supplied", false);
+      psLogMsg("pswarp", PS_LOG_INFO, "No background models supplied");
+    }
+
+    // Chip selection: turn on only the chips specified
+    char *chipLine = psMetadataLookupStr(&status, config->arguments, "CHIP_SELECTIONS");
+    if (status) {
+        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(PSWARP_ERR_ARGUMENTS, true, "Chip number %d doesn't exist in camera.\n", chipNum);
+                    return false;
+                }
+            }
+        }
+        psFree(chips);
+    }
+
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "NUM_INPUTS", PS_META_REPLACE, "input file sets", 1);
+    return true;
+}
+
+// read input file information from a metadata file
+// XXX for now, in the multi-input format, we disable PSF, BACKGROUND, and CHIP_SELECTIONS
+bool pswarpParseMultiInput (pmConfig *config, psMetadata *fileListMD) {
+
+    // the multi-input file consists of a set of metadata blocks, each with entries 
+    // for INPUT and (possibly) ASTROM, MASK, VARIANCE, ???
+
+    bool status = false;
+
+    for (int i = 0; i < fileListMD->list->n; i++) {
+	psMetadataItem *item = psMetadataGet(fileListMD, i);
+	if (item->type != PS_DATA_METADATA) {
+	    psError(PS_ERR_BAD_PARAMETER_TYPE, true, "Component %s of the input metadata is not of type METADATA", item->name);
+	    return false;
+	}
+
+	// next input metadata block of interest
+	psMetadata *fileBlockMD = item->data.md;
+
+	psString inputName = psMetadataLookupStr(&status, fileBlockMD, "INPUT"); // Name of image
+	if (!inputName || !strlen(inputName)) {
+	    psError(PS_ERR_UNKNOWN, false, "input file not found in metadata block %d (%s)", i, item->name);
+	    return false;
+	}
+	AddStringAsArray (config->arguments, inputName, "FILENAMES");
+
+	pmFPAfile *input = pswarpDefineInputFile (config, NULL, "PSWARP.INPUT", "FILENAMES", PM_FPA_FILE_IMAGE);
+	if (!input) {
+	    psError(psErrorCodeLast(), false, "Failed to build FPA from PSWARP.INPUT");
+	    return false;
+	}
+
+	psString astromName = psMetadataLookupStr(&status, fileBlockMD, "ASTROM"); // Name of astrom file
+	if (astromName && strlen(astromName)) {
+	    AddStringAsArray (config->arguments, astromName, "FILENAMES");
+
+	    pmFPAfile *astrom = pmFPAfileDefineFromArgs (&status, config, "PSWARP.ASTROM", "FILENAMES");
+	    if (!status) {
+		psLogMsg("pswarp", PS_LOG_INFO, "Failed to load file definition for %s", "PSWARP.ASTROM");
+	    }
+	    if (astrom) {
+		if ((astrom->type != PM_FPA_FILE_CMF) && (astrom->type != PM_FPA_FILE_WCS)) {
+		    psLogMsg("pswarp", PS_LOG_INFO, "%s is neither CMF nor WCS", "PSWARP.ASTROM");
+		} else {
+		    foundAstrom = true;
+		}
+	    }
+	    psLogMsg("pswarp", PS_LOG_INFO, "Astrometry source: %s", astrom ? "supplied" : "header");
+	}
+
+	pmFPAfile *mask = NULL;
+	psString maskName = psMetadataLookupStr(&status, fileBlockMD, "MASK"); // Name of mask
+	if (maskName && strlen(maskName)) {
+	    AddStringAsArray (config->arguments, maskName, "FILENAMES");
+
+	    mask = pswarpDefineInputFile (config, input, "PSWARP.MASK", "FILENAMES", PM_FPA_FILE_MASK);
+	}
+	if (!mask) {
+	    psLogMsg("pswarp", PS_LOG_INFO, "No mask supplied");
+	}
+
+	pmFPAfile *variance = NULL;
+	psString varianceName = psMetadataLookupStr(&status, fileBlockMD, "VARIANCE"); // Name of variance
+	if (varianceName && strlen(varianceName)) {
+	    AddStringAsArray (config->arguments, varianceName, "FILENAMES");
+
+	    variance = pswarpDefineInputFile (config, input, "PSWARP.VARIANCE", "FILENAMES", PM_FPA_FILE_VARIANCE);
+	}
+	if (variance) {
+	    foundVariance = true;
+	} else {
+	    psLogMsg("pswarp", PS_LOG_INFO, "No variance supplied");
+	}
+
+	// XXX for now we do not have an option to supply a background model set in multi input mode
+	psMetadataAddBool (config->arguments, PS_LIST_TAIL, "BACKGROUND.MODEL", PS_META_REPLACE, "no input background supplied", false);
+    }
+
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "NUM_INPUTS", PS_META_REPLACE, "input file sets", fileListMD->list->n);
+    return true;
+}
+
+// Define an input file based on name in config->arguments or config RUN block
+pmFPAfile *pswarpDefineInputFile(pmConfig *config,// Configuration
+				 pmFPAfile *bind,    // File to which to bind, or NULL
+				 char *filerule,     // Name of file rule
+				 char *argname,      // Argument name
+				 pmFPAfileType fileType // Type of file
+    )
+{
+    bool status;
+
+    pmFPAfile *file = NULL;
+    // look for the file on the argument list
+    if (bind) {
+        file = pmFPAfileBindFromArgs(&status, bind, config, filerule, argname);
+    } else {
+        file = pmFPAfileDefineFromArgs(&status, config, filerule, argname);
+    }
+    if (!status) {
+        psError(psErrorCodeLast(), false, "Failed to load file definition for %s", filerule);
+        return false;
+    }
+    if (!file) {
+        // look for the file on the RUN metadata
+        file = pmFPAfileDefineFromRun(&status, bind, config, filerule); // File to return
+        if (!status) {
+            psError(psErrorCodeLast(), false, "Failed to load file definition for %s", filerule);
+            return NULL;
+        }
+    }
+
+    if (!file) {
+        return NULL;
+    }
+
+    if (file->type != fileType) {
+        psError(PSWARP_ERR_CONFIG, true, "%s is not of type %s", filerule, pmFPAfileStringFromType(fileType));
+        return NULL;
+    }
+
+    return file;
+}
+
+bool AddStringAsArray (psMetadata *md, char *string, char *name) {
+
+    psArray *dummy = psArrayAlloc(1);   // Dummy array of filenames for this FPA
+    dummy->data[0] = psStringCopy(string);
+
+    psMetadataAddArray(md, PS_LIST_TAIL, name, PS_META_REPLACE, "string added as array", dummy);
+    psFree(dummy);
+    return true;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpPixelsLit.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpPixelsLit.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpPixelsLit.c	(revision 41091)
@@ -0,0 +1,84 @@
+/** @file pswarpPixelFraction.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-05 20:44:04 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdio.h>
+#include <pslib.h>
+#include <psmodules.h>
+
+#include "pswarp.h"
+
+bool pswarpPixelsLit(const pmReadout *readout, psMetadata *stats, const pmConfig *config)
+{
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_IMAGE_NON_NULL(readout->image, false);
+    PS_ASSERT_IMAGE_TYPE(readout->image, PS_TYPE_F32, false);
+    if (!readout->mask) {
+        // Can't do anything
+        return true;
+    }
+    PS_ASSERT_IMAGE_NON_NULL(readout->mask, false);
+    PS_ASSERT_IMAGES_SIZE_EQUAL(readout->mask, readout->image, false);
+    PS_ASSERT_IMAGE_TYPE(readout->mask, PS_TYPE_IMAGE_MASK, false);
+
+    if (!stats) {
+        // No point in continuing --- we record results to the statistics
+        return true;
+    }
+    PS_ASSERT_PTR_NON_NULL(config, false);
+    PS_ASSERT_METADATA_NON_NULL(config->arguments, false);
+
+    bool status;
+
+    // load the recipe
+    psMetadata *recipe = psMetadataLookupPtr (NULL, config->recipes, PSWARP_RECIPE);
+    if (!recipe) {
+        psError(PSPHOT_ERR_CONFIG, false, "missing recipe %s", PSWARP_RECIPE);
+        return false;
+    }
+
+    // output mask bits
+    psImageMaskType maskValue = psMetadataLookupImageMask(&status, recipe, "MASK.OUTPUT");
+    psAssert(status, "MASK.OUTPUT was not defined");
+
+    psImage *image = readout->image;    ///< Image of interest
+    psImage *mask = readout->mask;      ///< Mask image
+
+    int numCols = image->numCols, numRows = image->numRows; ///< Size of image
+
+    // Range of valid pixels
+    int xMin = INT_MAX, xMax = -INT_MAX, yMin = INT_MAX, yMax = -INT_MAX;
+
+    for (int y = 0; y < numRows; y++) {
+        for (int x = 0; x < numCols; x++) {
+	    if (mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x] & maskValue) { continue; }
+	    xMin = PS_MIN (xMin, x);
+	    xMax = PS_MAX (xMax, x);
+	    yMin = PS_MIN (yMin, y);
+	    yMax = PS_MAX (yMax, y);
+        }
+    }
+
+    if (stats) {
+	// XXX with multiple inputs (eg, output stacks -> exposure), these only represent the last input
+        psMetadataAddS32(stats, PS_LIST_TAIL, "RANGE.XMIN", PS_META_REPLACE, "Minimum valid x value", xMin);
+        psMetadataAddS32(stats, PS_LIST_TAIL, "RANGE.XMAX", PS_META_REPLACE, "Maximum valid x value", xMax);
+        psMetadataAddS32(stats, PS_LIST_TAIL, "RANGE.YMIN", PS_META_REPLACE, "Minimum valid y value", yMin);
+        psMetadataAddS32(stats, PS_LIST_TAIL, "RANGE.YMAX", PS_META_REPLACE, "Maximum valid y value", yMax);
+    }
+
+    return true;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpSetMaskBits.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpSetMaskBits.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpSetMaskBits.c	(revision 41091)
@@ -0,0 +1,97 @@
+/** @file pswarpSetMaskBits.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-06 03:10:36 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "pswarp.h"
+
+/** This function is called by the stand-alone pswarp program to set the mask values in the
+ * config file.  It sets the named mask values MASK.PSPHOT and MARK.PSPHOT in the PSPHOT
+ * recipe.  Functions or programs which call psphotReadout as a library function must set these
+ * named mask values in the PSPHOT recipe on their own.  This function should only be called
+ * after the first header of the input mask image has been loaded and the named mask bits
+ * updated in the config metadata.
+ */
+bool pswarpSetMaskBits (pmConfig *config)
+{
+    psImageMaskType maskIn = 0x00;                      // mask for the input image
+    psImageMaskType markIn = 0x00;                      // mark for the input image
+    psImageMaskType maskOut = 0x00;                     // mask for the output image
+
+    // this function sets the required single-image mask bits
+    if (!pmConfigMaskSetBits (&maskIn, &markIn, config)) {
+        psError (psErrorCodeLast(), false, "Unable to define the mask bit values");
+        return false;
+    }
+
+    // mask for non-linear flat regions (default to DETECTOR if not defined)
+    psImageMaskType badMask = pmConfigMaskGet("CONV.BAD", config);
+    if (!badMask) {
+        badMask = 0x01;
+        pmConfigMaskSet (config, "CONV.BAD", badMask);
+    }
+    maskOut |= badMask;
+
+    // mask for non-linear flat regions (default to DETECTOR if not defined)
+    psImageMaskType poorMask = pmConfigMaskGet("CONV.POOR", config);
+    if (!poorMask) {
+        poorMask = 0x02;
+        pmConfigMaskSet (config, "CONV.POOR", poorMask);
+    }
+    maskOut |= poorMask;
+
+    // the output image includes all of the bits from the input image
+    maskOut |= maskIn;
+
+    // search for an unset bit to use for MARK:
+    psImageMaskType markOut   = 0x00;
+    psImageMaskType markTrial = 0x01;
+
+    int nBits = sizeof(psImageMaskType) * 8;
+    for (int i = 0; !markOut && (i < nBits); i++) {
+        if (maskOut & markTrial) {
+            markTrial <<= 1;
+        } else {
+            markOut = markTrial;
+        }
+    }
+    if (!markOut) {
+        psError(PSWARP_ERR_CONFIG, true, "Unable to define the MARK bit mask: all bits taken!");
+        return false;
+    }
+
+    // update the pswarp recipe
+    psMetadata *warpRecipe = psMetadataLookupPtr (NULL, config->recipes, PSWARP_RECIPE);
+    if (!warpRecipe) {
+        psError(PSWARP_ERR_CONFIG, false, "missing recipe %s", PSWARP_RECIPE);
+        return false;
+    }
+
+    // set maskOut and markOut in the psphot recipe
+    // NOTE: psphot works on the output images, not input images, so set the MARK and MASK correctly here
+    psMetadataAddImageMask (warpRecipe, PS_LIST_TAIL, "MASK.INPUT",  PS_META_REPLACE, "user-defined mask", maskIn);
+    psMetadataAddImageMask (warpRecipe, PS_LIST_TAIL, "MARK.INPUT",  PS_META_REPLACE, "user-defined mask", markIn);
+    psMetadataAddImageMask (warpRecipe, PS_LIST_TAIL, "MASK.OUTPUT", PS_META_REPLACE, "user-defined mask", maskOut);
+    psMetadataAddImageMask (warpRecipe, PS_LIST_TAIL, "MARK.OUTPUT", PS_META_REPLACE, "user-defined mask", markOut);
+
+    // update the psphot recipe
+    psMetadata *psphotRecipe = psMetadataLookupPtr (NULL, config->recipes, PSPHOT_RECIPE);
+    if (!psphotRecipe) {
+        psError(PSWARP_ERR_CONFIG, false, "missing recipe %s", PSPHOT_RECIPE);
+        return false;
+    }
+
+    // set maskOut and markOut in the psphot recipe
+    // NOTE: psphot works on the output images, not input images, so set the MARK and MASK correctly here
+    psMetadataAddImageMask (psphotRecipe, PS_LIST_TAIL, "MARK.PSPHOT", PS_META_REPLACE, "user-defined mask", markOut);
+    psMetadataAddImageMask (psphotRecipe, PS_LIST_TAIL, "MASK.PSPHOT", PS_META_REPLACE, "user-defined mask", maskOut);
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpSetThreads.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpSetThreads.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpSetThreads.c	(revision 41091)
@@ -0,0 +1,32 @@
+/** @file pswarpSetThreads.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-05 20:44:04 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "pswarp.h"
+
+/** each thread runs this function, starting a new job when it finished with an old one
+ * it is called with a (void *) pointer to its own thread pointer
+ */
+bool pswarpThread_pswarpTransformTile(psThreadJob *job)
+{
+    pswarpTransformTileArgs *args = job->args->data[0];
+    return pswarpTransformTile(args);
+}
+
+bool pswarpSetThreads(void)
+{
+    psThreadTask *task = psThreadTaskAlloc("PSWARP_TRANSFORM_TILE", 1);
+    task->function = &pswarpThread_pswarpTransformTile;
+    psThreadTaskAdd(task);
+    psFree(task);
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpStatsFile.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpStatsFile.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpStatsFile.c	(revision 41091)
@@ -0,0 +1,94 @@
+/** @file pswarpStatsFile.c
+ *
+ *  @brief functions for managing the stats file (or return NULL, or exit with an error)
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.38 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-13 21:54:32 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "pswarp.h"
+
+static void pswarpStatsFileFree (pswarpStatsFile *statsFile) {
+    psFree (statsFile->name);
+    psFree (statsFile->md);
+}
+
+pswarpStatsFile *pswarpStatsFileAlloc () {
+
+    pswarpStatsFile *statsFile = psAlloc(sizeof(pswarpStatsFile));
+    psMemSetDeallocator(statsFile, (psFreeFunc)pswarpStatsFileFree);
+
+    statsFile->f = NULL;
+    statsFile->name = NULL;
+    statsFile->md = NULL;
+
+    return statsFile;
+}
+
+// Open the statistics file
+pswarpStatsFile *pswarpStatsFileOpen (pmConfig *config) {
+
+    bool mdok;
+
+    char *name = psMetadataLookupStr(&mdok, config->arguments, "STATS"); ///< Filename for statistics
+
+    pswarpStatsFile *statsFile = pswarpStatsFileAlloc ();
+    statsFile->name = psMemIncrRefCounter(name);
+
+    if (!mdok) return statsFile; // XXX this is probably a config error, but treat as ok for now
+    if (!name) return statsFile;
+    if (strlen(name) == 0) return statsFile;
+
+    psString resolved = pmConfigConvertFilename(name, config, true, true);
+
+    statsFile->f = fopen(resolved, "w");
+    if (!statsFile->f) {
+	psError(PS_ERR_IO, true, "Unable to open statistics file %s for writing.\n", resolved);
+	psFree(resolved);
+        pswarpCleanup(config, statsFile);
+    }
+    psFree(resolved);
+
+    statsFile->md = psMetadataAlloc();
+    psMetadataAddS32(statsFile->md, PS_LIST_TAIL, "QUALITY", 0, "No problems", 0);
+
+    return statsFile;
+}
+
+bool pswarpStatsFileSave (pmConfig *config, pswarpStatsFile *statsFile) {
+
+    if (!statsFile) return true;
+    if (!statsFile->md) return true;
+
+    // Write out summary statistics
+    psMetadataAddF32(statsFile->md, PS_LIST_TAIL, "DT_WARP", 0, "Time for warp completion", psTimerMark("pswarp"));
+
+    // convert the psMetadata to a string block
+    const char *statsMDC = psMetadataConfigFormat(statsFile->md);
+    if (!statsMDC) {
+	psError(psErrorCodeLast(), false, "Unable to get statistics file.");
+	return false;
+    }
+
+    if (fprintf(statsFile->f, "%s", statsMDC) != strlen(statsMDC)) {
+	psError(PSWARP_ERR_IO, true, "Unable to write statistics file.");
+	return false;
+    }
+    psFree(statsMDC);
+
+    if (fclose(statsFile->f) == EOF) {
+	psError(PSWARP_ERR_IO, true, "Unable to close statistics file.");
+	statsFile->f = NULL;
+	return false;
+    }
+    statsFile->f = NULL;
+
+    pmConfigRunFilenameAddWrite(config, "STATS", statsFile->name);
+    psFree(statsFile);
+
+    return true;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpThreads.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpThreads.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpThreads.c	(revision 41091)
@@ -0,0 +1,66 @@
+/** @file pswarpThreads.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-05 20:44:04 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "pswarp.h"
+
+typedef enum {
+  PSWARP_TRANSFORM_TILE
+} pswarpJobType;
+
+typedef struct {
+  pswarpJobType type;
+  void *opts;
+} pswarpJob;
+
+static psArray *jobs = NULL;
+static pthread_t *threads = NULL;
+
+bool pswarpThreadsAddJob (pwarpJob *job) {
+
+  if (jobs == NULL) {
+    jobs = psArrayAlloc (16);
+  }
+
+  psArrayAdd (jobs, job);
+  return true;
+}
+
+bool pswarpThreadsLaunchJobs () {
+
+  while () {
+    while ((job = psListGetAndRemove (jobs, PS_LIST_HEAD)) == NULL) {
+      usleep (50000);
+    }
+
+    switch (job->type) {
+      case PSWARP_TRANSFORM_TILE:
+	status = pswarpTransformTile ((pswarpTransformTileOpts *)job->opts);
+	// send a message somewhere if the job fails
+	break;
+    }
+  }  
+}
+
+/**
+ * create a pool of Nthreads
+ */
+bool pswarpThreadsCreate (int nThreads) {
+
+  threads = (pthread_t *) psAlloc (sizeof(pthread_t));
+
+  for (int i = 0; i < nThreads; i++) {
+    pthread_create (&threads[i], NULL, &function, NULL);
+  }
+
+  return true;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpTransformReadout.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpTransformReadout.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpTransformReadout.c	(revision 41091)
@@ -0,0 +1,225 @@
+/** @file pswarpTransformReadout.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.17 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-18 02:58:59 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "pswarp.h"
+
+/**
+ * NOTE: in this function, the coordinates are transformed from the OUTPUT to the INPUT
+ */
+bool pswarpTransformReadout(pmReadout *output, pmReadout *input, pmConfig *config, bool backgroundWarp)
+{
+    // XXX this implementation currently ignores the use of the region
+    psImage *region = NULL;             ///< Region to transform
+
+    psTimerStart("warp");
+
+    // Get warp parameters
+    bool mdok;                          ///< Status of MD lookup
+    int nGridX = psMetadataLookupS32(NULL, config->arguments, "GRID.NX"); ///< Number of grid points in x
+    int nGridY = psMetadataLookupS32(NULL, config->arguments, "GRID.NY"); ///< Number of grid points in y
+    psImageInterpolateMode interpolationMode = psMetadataLookupS32(NULL, config->arguments,
+                                                                   "INTERPOLATION.MODE"); ///< Mode for interp
+
+    int numKernels = psMetadataLookupS32(NULL, config->arguments, "INTERPOLATION.NUM"); ///< Number of kernels
+
+    // load the recipe
+    psMetadata *recipe = psMetadataLookupPtr(NULL, config->recipes, PSWARP_RECIPE);
+    psAssert (recipe, "missing recipe %s", PSWARP_RECIPE);
+
+    // output mask bits
+    psImageMaskType maskIn   = psMetadataLookupImageMask(&mdok, recipe, "MASK.INPUT");
+    psImageMaskType maskPoor = pmConfigMaskGet("CONV.POOR", config);
+    if (!maskPoor) {
+        maskPoor = pmConfigMaskGet("POOR.WARP", config);
+    }
+    psImageMaskType maskBad  = pmConfigMaskGet("CONV.BAD", config);
+    if (!maskBad) {
+        maskBad  = pmConfigMaskGet("BAD.WARP", config);
+    }
+    psAssert(mdok, "MASK.INPUT was not defined");
+
+    if (!input->covariance) {
+        input->covariance = psImageCovarianceNone();
+    }
+
+    float poorFrac = psMetadataLookupF32(NULL, config->arguments, "POOR.FRAC"); ///< Flux fraction for "poor"
+
+    // pswarpMapGridFromImage builds a set of locally-linear maps which convert the
+    // output coordinates to input coordinates
+    pswarpMapGrid *grid = pswarpMapGridFromImage(input, output, nGridX, nGridY);
+    
+    // XXX optionally modify the grid based on this result and force the maxError < XXX
+    double maxError = pswarpMapGridMaxError(grid); // Maximum (positional) error from using grid
+    psLogMsg("pswarp", 3, "maximum error using this grid sampling: %lf\n", maxError);
+
+    // Get range of interest
+    int xOutMin, xOutMax, yOutMin, yOutMax; ///< Output pixel range
+    pswarpMatchRange(&xOutMin, &yOutMin, &xOutMax, &yOutMax, input, output);
+
+    // Check the range of the grid coordinates
+#define CHECK_GRID_RANGE() { \
+        xGridMin = PS_MIN(xGridMin, xGrid); \
+        xGridMax = PS_MAX(xGridMax, xGrid); \
+        yGridMin = PS_MIN(yGridMin, yGrid); \
+        yGridMax = PS_MAX(yGridMax, yGrid); \
+    }
+
+    int xGridMin = nGridX, xGridMax = 0, yGridMin = nGridY, yGridMax = 0; ///< Grid range
+    int xGrid, yGrid;                   ///< Grid coordinates
+    pswarpMapGridSetGrid(grid, xOutMin, yOutMin, &xGrid, &yGrid);
+    CHECK_GRID_RANGE();
+    pswarpMapGridSetGrid(grid, xOutMin, yOutMax, &xGrid, &yGrid);
+    CHECK_GRID_RANGE();
+    pswarpMapGridSetGrid(grid, xOutMax, yOutMin, &xGrid, &yGrid);
+    CHECK_GRID_RANGE();
+    pswarpMapGridSetGrid(grid, xOutMax, yOutMax, &xGrid, &yGrid);
+    CHECK_GRID_RANGE();
+
+    if (!pmReadoutMaskNonfinite(input, pmConfigMaskGet("SAT", config))) {
+        psError(psErrorCodeLast(), false, "Unable to mask non-finite pixels in input.");
+        return false;
+    }
+
+    // Interpolation options : move these from the arguments to explicit assignments
+    psImageInterpolation *interp = psImageInterpolationAlloc(interpolationMode, input->image,
+                                                             input->variance, input->mask, maskIn,
+                                                             NAN, NAN, maskBad, maskPoor, poorFrac,
+                                                             numKernels);
+
+    if (input->variance && !output->variance) {
+        output->variance = psImageAlloc(output->image->numCols, output->image->numRows, PS_TYPE_F32);
+        psImageInit(output->variance, NAN);
+    }
+    if ((input->mask || maskPoor || maskBad) && !output->mask) {
+        output->mask = psImageAlloc(output->image->numCols, output->image->numRows, PS_TYPE_IMAGE_MASK);
+        psImageInit(output->mask, maskBad);
+    }
+
+    // Ensure threading is off for the covariance calculation, since we are threading on a different level.
+    psImageCovarianceSetThreads(false);
+
+    psAssert (xGridMin >= 0, "xGridMin too small\n");
+    psAssert (yGridMin >= 0, "yGridMin too small\n");
+    psAssert (xGridMax < grid->nXpts, "xGridMax too big\n");
+    psAssert (yGridMax < grid->nYpts, "yGridMax too big\n");
+
+    // fprintf (stderr, "warp %d,%d - %d,%d\n", xGridMin, yGridMin, xGridMax, yGridMax);
+
+    // create jobs and supply them to the threads
+    for (int gridY = yGridMin; gridY <= yGridMax; gridY++) {
+        for (int gridX = xGridMin; gridX <= xGridMax; gridX++) {
+            pswarpTransformTileArgs *args = pswarpTransformTileArgsAlloc();
+            args->input = psMemIncrRefCounter(input);
+            args->output = psMemIncrRefCounter(output);
+            args->grid = psMemIncrRefCounter(grid);
+            args->interp = psMemIncrRefCounter(interp);
+            args->region = psMemIncrRefCounter(region);
+
+            args->gridX = gridX;
+            args->gridY = gridY;
+            args->goodPixels = 0;
+
+	    if (backgroundWarp) {
+	      args->background_warping = true;
+	      args->offset_x = psMetadataLookupF32(NULL,config->arguments,"BKG_WARP_XOFFSET");
+	      args->offset_y = psMetadataLookupF32(NULL,config->arguments,"BKG_WARP_YOFFSET");
+	    }
+	    
+            // allocate a job
+            psThreadJob *job = psThreadJobAlloc ("PSWARP_TRANSFORM_TILE");
+            psArrayAdd(job->args, 1, args);
+            if (!psThreadJobAddPending(job)) {
+                psError(psErrorCodeLast(), false, "Unable to warp image.");
+                return false;
+            }
+            psFree(args);
+        }
+    }
+
+    // wait for the threads to finish and manage results
+    // wait here for the threaded jobs to finish
+    if (!psThreadPoolWait (false, true)) {
+        psError(psErrorCodeLast(), false, "Unable to interpolate image.");
+        return false;
+    }
+
+    // each job records its own goodPixel values; sum them here
+    // we have only supplied one type of job, so we can assume the types here
+    psThreadJob *job = NULL;
+    int xMin = output->image->numCols, xMax = 0, yMin = output->image->numRows, yMax = 0; // Bounds
+    int goodPixels = psMetadataLookupS32(&mdok, output->analysis, PSWARP_ANALYSIS_GOODPIX); // Number of pixels
+    psList *covariances = psMetadataLookupPtr(&mdok, output->analysis,
+                                              PSWARP_ANALYSIS_COVARIANCES); // Collection of covar. matrices
+    if (!covariances) {
+        covariances = psListAlloc(NULL);
+        psMetadataAddList(output->analysis, PS_LIST_TAIL, PSWARP_ANALYSIS_COVARIANCES, 0,
+                          "Collection of covariance matrices", covariances);
+        psFree(covariances);            // Drop reference; still have the copy on the analysis metadata
+    }
+    double jacobian = psMetadataLookupF64(&mdok, output->analysis, PSWARP_ANALYSIS_JACOBIAN); // Jacobian
+    if (!isfinite(jacobian)) {
+        jacobian = 0.0;
+    }
+
+    while ((job = psThreadJobGetDone()) != NULL) {
+        if (job->args->n < 1) {
+            fprintf (stderr, "error with job\n");
+        } else {
+            pswarpTransformTileArgs *args = job->args->data[0];
+            goodPixels += args->goodPixels;
+            xMin = PS_MIN(args->xMin, xMin);
+            xMax = PS_MAX(args->xMax, xMax);
+            yMin = PS_MIN(args->yMin, yMin);
+            yMax = PS_MAX(args->yMax, yMax);
+            if (args->covariance) {
+                psListAdd(covariances, PS_LIST_TAIL, args->covariance);
+            }
+            if (args->goodPixels > 0 && isfinite(args->jacobian)) {
+                jacobian += args->jacobian * args->goodPixels;
+            }
+
+	    
+        }
+        psFree(job);
+    }
+    psFree(grid);
+
+    psMetadataAddS32(output->analysis, PS_LIST_TAIL, PSWARP_ANALYSIS_GOODPIX, PS_META_REPLACE,
+                     "Number of good pixels", goodPixels);
+    psMetadataAddF64(output->analysis, PS_LIST_TAIL, PSWARP_ANALYSIS_JACOBIAN, PS_META_REPLACE,
+                     "Jacobian of transformation", jacobian);
+
+    if (xMin < xMax && yMin < yMax) {
+        psTrace("pswarp.transform", 1, "Bounds [%d:%d,%d:%d]\n", xMin, xMax, yMin, yMax);
+    } else {
+        psTrace("pswarp.transform", 1, "No overlap\n");
+    }
+    psFree(interp);
+
+    if (goodPixels > 0 && !backgroundWarp && psMetadataLookupBool(&mdok, recipe, "SOURCES")) {
+	if (!pswarpTransformSources(output, input, config)) {
+	    psError(psErrorCodeLast(), false, "Unable to transform sources.");
+	    return false;
+        }
+    }
+
+    if (goodPixels > 0) {
+        // Data is only written out if there are good pixels
+        output->data_exists = true;
+        output->parent->data_exists = true;
+        output->parent->parent->data_exists = true;
+    }
+
+    psLogMsg("pswarp", 3, "warping analysis: %f sec\n", psTimerMark ("warp"));
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpTransformSources.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpTransformSources.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpTransformSources.c	(revision 41091)
@@ -0,0 +1,155 @@
+/** @file pswarpTransformSources.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-05 20:44:04 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "pswarp.h"
+
+#define SOURCE_ARRAY_BUFFER 100         ///< Size to grow the array of sources at a time
+
+/**
+ * NOTE: in this function, the coordinates are transformed from the OUTPUT to the INPUT
+ */
+bool pswarpTransformSources(pmReadout *output, pmReadout *input, pmConfig *config)
+{
+
+    // find the output pixel range
+    int minX, minY, maxX, maxY;
+    pswarpMatchRange (&minX, &minY, &maxX, &maxY, input, output);
+
+    // Get warp parameters
+    bool mdok;
+    int nGridX = psMetadataLookupS32(NULL, config->arguments, "GRID.NX");
+    int nGridY = psMetadataLookupS32(NULL, config->arguments, "GRID.NY");
+
+    // Transform sources
+    pmDetections *inDetections = psMetadataLookupPtr(&mdok, input->analysis, "PSPHOT.DETECTIONS"); // Sources in source
+    if (!inDetections) return true;
+    psArray *inSources = inDetections->allSources;
+    if (!inSources) return true;
+
+    pswarpMapGrid *sourceGrid = pswarpMapGridFromImage(output, input, nGridX, nGridY); // Grid for sources
+
+    pmDetections *outDetections = psMemIncrRefCounter(psMetadataLookupPtr(&mdok, output->analysis, "PSPHOT.DETECTIONS")); // Target sources
+    if (!outDetections) {
+        outDetections = pmDetectionsAlloc();
+	psMetadataAddPtr(output->analysis, PS_LIST_TAIL, "PSPHOT.DETECTIONS", PS_DATA_ARRAY , "Warped sources", outDetections);
+    }
+    psArray *outSources = outDetections->allSources;
+    if (!outSources) {
+        outDetections->allSources = psArrayAllocEmpty(SOURCE_ARRAY_BUFFER);
+        outSources = outDetections->allSources;
+    }
+
+    // XXX it is probably not necessary to use the locally linear transformations we should
+    // be using the full astrometry: since there are 100 - 1000 fewer sources than pixels,
+    // this does not cost us so much time.
+    for (int i = 0; i < inSources->n; i++) {
+        pmSource *source = inSources->data[i]; ///< Source of interest
+        pmModel *model = source->modelPSF; ///< Model for this source
+        float xIn, yIn;             ///< Coordinates of source
+        xIn = model->params->data.F32[PM_PAR_XPOS] - input->image->col0;
+        yIn = model->params->data.F32[PM_PAR_YPOS] - input->image->row0;
+
+        int xGrid, yGrid;           ///< Grid coordinates for local map
+        if (!pswarpMapGridSetGrid(sourceGrid, xIn, yIn, &xGrid, &yGrid)) {
+            psError(psErrorCodeLast(), false, "Unable to get grid coordinates for source at %f,%f\n",
+                    xIn, yIn);
+            psFree(outDetections);
+            psFree(sourceGrid);
+            return false;
+        }
+        if (xGrid < 0 || xGrid >= sourceGrid->nXpts || yGrid < 0 || yGrid >= sourceGrid->nYpts) {
+            // It's not even on the grid
+            // XXX how can this happen?
+            continue;
+        }
+
+        pswarpMap *map = sourceGrid->maps[xGrid][yGrid]; ///< Locally linear transformation
+        double xOut, yOut;          ///< Output coordinates
+        if (!pswarpMapApply(&xOut, &yOut, map, xIn, yIn)) {
+            psError(psErrorCodeLast(), false, "Unable to transform coordinates for source at %f,%f\n",
+                    xIn, yIn);
+            psFree(outDetections);
+            psFree(sourceGrid);
+            return false;
+        }
+        xOut += output->image->col0;
+        yOut += output->image->row0;
+        if (xOut < minX || xOut > maxX || yOut < minY || yOut > maxY) {
+            // It's not in the output image
+            continue;
+        }
+
+        // Generate the new source in the output frame
+        //
+        // Magnitudes will be off if there's any change in scale, but for our purposes (mainly x,y and
+        // relative flux) that's OK.
+        pmSource *new = pmSourceAlloc(); ///< New source
+        new->peak = pmPeakAlloc(xOut, yOut, source->peak->detValue, PM_PEAK_LONE);
+        new->peak->rawFlux = source->peak->rawFlux;
+        new->peak->smoothFlux = source->peak->smoothFlux;
+        new->type = source->type;
+        new->mode = source->mode;
+        new->psfMag = source->psfMag;
+        new->extMag = source->extMag;
+        new->psfMagErr = source->psfMagErr;
+        new->apMag = source->apMag;
+        new->pixWeightNotBad = source->pixWeightNotBad;
+        new->pixWeightNotPoor = source->pixWeightNotPoor;
+        new->psfChisq = source->psfChisq;
+        new->crNsigma = source->crNsigma;
+        new->extNsigma = source->extNsigma;
+        new->sky = source->sky;
+        new->skyErr = source->skyErr;
+        new->apRadius = source->apRadius;
+
+        new->modelPSF = pmModelAlloc(source->modelPSF->type);
+        new->modelPSF->params->data.F32[PM_PAR_I0]=
+          (isfinite(new->psfMag) ? pow(10.0, -0.4*new->psfMag) : NAN);
+        new->modelPSF->dparams->data.F32[PM_PAR_I0]=
+          (isfinite(new->psfMag) ? new->psfMagErr*pow(10.0, -0.4*new->psfMag) : NAN);
+
+#if 0
+        // XXX Note that this will not set the correct axes
+	psEllipseAxes axes = pmPSF_ModelToAxes(source->modelPSF->params->data.F32, 20.0, source->modelPSF->class->useReff);
+        pmPSF_AxesToModel(new->modelPSF->params->data.F32, axes, new->modelPSF->class->useReff);
+#endif
+
+        // Propagate the position erorrs
+        float dxIn, dyIn;           // Errors in input coordinates
+        dxIn = model->dparams->data.F32[PM_PAR_XPOS];
+        dyIn = model->dparams->data.F32[PM_PAR_YPOS];
+
+        float dxOut, dyOut;         // Errors in output coordinates
+        dxOut = sqrt(PS_SQR(map->Xx * dxIn) + PS_SQR(map->Xy * dyIn));
+        dyOut = sqrt(PS_SQR(map->Yx * dxIn) + PS_SQR(map->Yy * dyIn));
+
+        new->modelPSF->params->data.F32[PM_PAR_XPOS] = xOut;
+        new->modelPSF->params->data.F32[PM_PAR_YPOS] = yOut;
+        new->modelPSF->dparams->data.F32[PM_PAR_XPOS] = dxOut;
+        new->modelPSF->dparams->data.F32[PM_PAR_YPOS] = dyOut;
+
+        new->modelPSF->chisq = model->chisq;
+        new->modelPSF->chisqNorm = model->chisqNorm;
+        new->modelPSF->mag = model->mag;
+        new->modelPSF->nDOF = model->nDOF;
+        new->modelPSF->nIter = model->nIter;
+        new->modelPSF->flags = model->flags;
+        new->modelPSF->fitRadius = model->fitRadius;
+
+        psArrayAdd(outSources, SOURCE_ARRAY_BUFFER, new);
+        psFree(new);                // Drop reference
+    }
+    psFree(sourceGrid);
+    psFree(outDetections);             // Drop reference
+    return true;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpTransformTile.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpTransformTile.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpTransformTile.c	(revision 41091)
@@ -0,0 +1,171 @@
+/** @file pswarpTransformTile.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-06 03:10:36 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "pswarp.h"
+
+static void transformTileArgsFree(pswarpTransformTileArgs *args)
+{
+    psFree(args->input);
+    psFree(args->output);
+    psFree(args->grid);
+    psFree(args->interp);
+    psFree(args->region);
+    psFree(args->covariance);
+    return;
+}
+
+pswarpTransformTileArgs *pswarpTransformTileArgsAlloc(void)
+{
+    pswarpTransformTileArgs *args = psAlloc(sizeof(pswarpTransformTileArgs));
+    psMemSetDeallocator(args, (psFreeFunc)transformTileArgsFree);
+
+    args->input = NULL;
+    args->output = NULL;
+    args->grid = NULL;
+    args->interp = NULL;
+    args->region = NULL;
+
+    args->gridX = 0;
+    args->gridY = 0;
+
+    args->goodPixels = 0;
+    args->xMin = PS_MAX_S32;
+    args->xMax = PS_MIN_S32;
+    args->yMin = PS_MAX_S32;
+    args->yMax = PS_MIN_S32;
+    args->covariance = NULL;
+    args->jacobian = NAN;
+
+    args->background_warping = false;
+    args->offset_x = 0.0;
+    args->offset_y = 0.0;
+    
+    return args;
+}
+
+bool pswarpTransformTile(pswarpTransformTileArgs *args)
+{
+    pmReadout *input = args->input;        // Input readout
+    psImage *inImage = input->image, *inMask = input->mask; // Input images
+    pmReadout *output = args->output;      // Output readout
+    psImage *outImage = output->image, *outVariance = output->variance, *outMask = output->mask; // Outputs
+
+    int inNumCols = inImage->numCols, inNumRows = inImage->numRows; ///< Size of input image
+    int outNumCols = outImage->numCols, outNumRows = outImage->numRows; ///< Size of output image
+    int outCol0 = outImage->col0, outRow0 = outImage->row0; ///< Offset of output image
+
+    psPlane minPt, maxPt;               ///< Minimum and maximum points for this tile
+    pswarpMapGridCoordRange(args->grid, args->gridX, args->gridY, &minPt, &maxPt);
+
+    // Dereference images for convenience
+    psF32 **outImageData     = outImage->data.F32;
+    psF32 **outVarData       = outVariance ? outVariance->data.F32 : NULL;
+    psImageMaskType **outMaskData = outMask ? outMask->data.PS_TYPE_IMAGE_MASK_DATA : NULL;
+    psImageMaskType **inMaskData  = inMask ? inMask->data.PS_TYPE_IMAGE_MASK_DATA : NULL;
+
+    pswarpMap *map = args->grid->maps[args->gridX][args->gridY]; ///< Map for this tile
+    psImage *region = args->region;     ///< Region to transform
+
+    /** Bounds for iteration */
+    int xMin = PS_MAX(minPt.x, 0);
+    int xMax = PS_MIN(maxPt.x, outNumCols);
+    int yMin = PS_MAX(minPt.y, 0);
+    int yMax = PS_MIN(maxPt.y, outNumRows);
+
+    double jacobian = fabs(map->Xx * map->Yy - map->Yx * map->Xy); // Jacobian of transformation
+    double jacobian2 = PS_SQR(jacobian);                     // Square Jacobian
+
+    // Iterate over the output image pixels (parent frame)
+    long goodPixels = 0;                ///< Number of input pixels landing on the output image
+
+    for (int y = yMin; y < yMax; y++) {
+
+      int yOut = y - outRow0; ///< Position on output image
+
+        for (int x = xMin; x < xMax; x++) {
+            // Only transform those pixels requested
+            if (region && region->data.PS_TYPE_IMAGE_MASK_DATA[y][x]) {
+                continue;
+            }
+
+	    int xOut = x - outCol0;
+
+	    // XXX the existing image may already have valid data -- probably should keep 
+	    // the best, but for the moment, just keep the pixel which is not NAN
+	    if (isfinite(outImageData[yOut][xOut])) continue;
+
+            // pswarpMapApply converts the output coordinate (x,y) to the input coordinate.
+            // both are in the parent frames of the input and output images.
+            double xIn, yIn;            // Input pixel coordinates
+            pswarpMapApply(&xIn, &yIn, map, x + 0.5, y + 0.5);
+
+	    // This needs to use a more reliable method to do this offset and limiting
+	    //	    if (args->interp->mode == 8) {
+	    if (args->background_warping) {
+	      //	      double xOffset = 177.0 / 400.0; // (modelsize * modelbinning - xsize) / 2.0
+	      //	      double yOffset = 166.0 / 400.0; // (modelsize * modelbinning - ysize) / 2.0
+	      xIn += args->offset_x;
+	      yIn += args->offset_y;
+
+	      if ((xIn > inNumCols - args->offset_x)||
+		  (yIn > inNumRows - args->offset_y)||
+		  (xIn < args->offset_x)||
+		  (yIn < args->offset_y)) {
+		continue;
+	      }
+	    }
+	    
+            if (xIn < 0 || xIn >= inNumCols || yIn < 0 || yIn >= inNumRows) {
+                continue;
+            }
+
+            // psImagePixelInterpolate determines the value at pixel coordinate (x,y) in child coordinates
+            double imageValue, varValue; // Value of image and variance map
+            psImageMaskType maskValue = inMaskData ? inMaskData[(int)yIn][(int)xIn] : 0; // Value of mask
+            if (!psImageInterpolate(&imageValue, &varValue, &maskValue, xIn, yIn, args->interp)) {
+                psError(psErrorCodeLast(), false, "Unable to interpolate image.");
+                return false;
+            }
+
+            if (outImageData) {
+	      // XXX TEST outImageData[yOut][xOut] = value;
+	      outImageData[yOut][xOut] = imageValue * jacobian;
+            }
+            if (outVarData) {
+                outVarData[yOut][xOut] = varValue * jacobian2;
+            }
+            if (outMaskData) {
+                outMaskData[yOut][xOut] = maskValue;
+            }
+
+            goodPixels++;
+        }
+    }
+
+    if (goodPixels > 0) {
+        float xOut = 0.5 * (xMin + xMax), yOut = 0.5 * (yMin + yMax); // Position of interest on output
+        double xIn, yIn;                // Position of interest on input
+        pswarpMapApply(&xIn, &yIn, map, xOut + 0.5, yOut + 0.5);
+        psKernel *kernel = psImageInterpolationKernel(xIn, yIn, args->interp->mode); // Interpolation kernel
+        args->covariance = psImageCovarianceCalculate(kernel, args->input->covariance);
+        args->jacobian = sqrt(jacobian);
+        psFree(kernel);
+    }
+
+    args->goodPixels = goodPixels;
+    args->xMin = xMin;
+    args->xMax = xMax;
+    args->yMin = yMin;
+    args->yMax = yMax;
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpUpdateMetadata.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpUpdateMetadata.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpUpdateMetadata.c	(revision 41091)
@@ -0,0 +1,196 @@
+/** @file pswarpUpdateMetadata.c
+ *
+ *  @brief update generic metadata info for warp
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @date $Date: 2009-02-13 21:54:32 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "pswarp.h"
+#define WCS_NONLIN_TOL 0.001            // Non-linear tolerance for header WCS
+
+// once the output fpa elements have been built, loop over the fpa and generate metadata
+bool pswarpUpdateMetadata (pmFPA *output, pmFPA *skycell, pmFPA *input, pmFPA *astrom, pmConfig *config, bool fullImage)  {
+
+    pmFPAview *view = pmFPAviewAlloc(0);
+    
+    // check output astrometry mode
+    bool bilevelAstrometry = psMetadataLookupBool (NULL, skycell->analysis, "ASTROMETRY.BILEVEL");
+
+    psString refcat = NULL;
+
+    if ((output)&&(output->analysis)) {    
+      psMetadataItem *refItem = psMetadataLookup(output->analysis, "REFERENCE_CATALOG");
+      if (refItem) {
+	refcat = psMetadataLookupStr (NULL, output->analysis, "REFERENCE_CATALOG");
+      }
+    }
+    pmChip *chip;
+    while ((chip = pmFPAviewNextChip (view, output, 1)) != NULL) {
+        psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+	// use this in output metadata info (MD5 sums)
+	const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME");
+
+        pmCell *cell;
+        while ((cell = pmFPAviewNextCell (view, output, 1)) != NULL) {
+            psTrace ("pswarp", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+	    // use this in output metadata info (MD5 sums)
+	    const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME");
+
+            // process each of the readouts
+            pmReadout *readout;
+            while ((readout = pmFPAviewNextReadout(view, output, 1)) != NULL) {
+	      // skip empty output readouts
+                if (!readout->data_exists) continue;
+    
+		if (fullImage) {
+		    // Set covariance matrix for output
+		    bool status = false;
+		    psList *covariances = psMetadataLookupPtr(&status, readout->analysis, PSWARP_ANALYSIS_COVARIANCES); // Covariance matrices
+		    psAssert(covariances, "Should be there");
+		    psArray *covars = psListToArray(covariances); // Array of covariance matrices
+		    if (covars->n) {
+		      psKernel *covar = psImageCovarianceAverage(covars);
+		      psMetadataRemoveKey(readout->analysis, PSWARP_ANALYSIS_COVARIANCES);
+
+		      // Correct covariance matrix scale for the mean (square root of the) Jacobian
+		      double jacobian = psMetadataLookupF64(NULL, readout->analysis, PSWARP_ANALYSIS_JACOBIAN); // Jacobian
+		      int goodPixels = psMetadataLookupS32(NULL, readout->analysis, PSWARP_ANALYSIS_GOODPIX);   // Good pixels
+		      jacobian /= goodPixels;
+		      readout->covariance = psImageCovarianceScale(covar, jacobian);
+		      psFree(covar);
+
+		      if (readout->variance) {
+			psImageCovarianceTransfer(readout->variance, readout->covariance);
+		      }
+
+		      psFree(covars);
+		    }
+		}
+
+		// Add MD5 information for readout
+		psString headerName = NULL; ///< Header name for MD5
+		psVector *md5 = psImageMD5(readout->image); ///< md5 hash
+		psString md5string = psMD5toString(md5); ///< String
+		
+		pmHDU *hdu = pmHDUFromReadout(readout);
+		psStringAppend(&headerName, "MD5_%s_%s_%d", chipName, cellName, view->readout);
+		psMetadataAddStr(hdu->header, PS_LIST_TAIL, headerName, PS_META_REPLACE, "Image MD5", md5string);
+		psFree(md5);
+		psFree(md5string);
+		psFree(headerName);
+
+		bool status;
+		psString keyword = NULL;
+		psString mapstring = NULL;
+
+		// we (should) have an array of chipnames on the analysis
+		psArray *chipnames = psMetadataLookupPtr(&status, readout->analysis, PSWARP_ANALYSIS_CHIPNAMES); 
+		for (int i = 0; chipnames && (i < chipnames->n); i++) {
+		  psStringAppend (&keyword, "SRC_%04d", i);
+		  psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "input image", chipnames->data[i]);
+		  psFree (keyword);
+		}
+
+		// we (should) have an array of chipnames on the analysis
+		psArray *chipRegions = psMetadataLookupPtr(&status, readout->analysis, PSWARP_ANALYSIS_CHIPREGIONS); 
+		for (int i = 0; chipRegions && (i < chipRegions->n); i++) {
+		  psStringAppend (&keyword, "SEC_%04d", i);
+		  psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "input image", chipRegions->data[i]);
+		  psFree (keyword);
+		}
+
+		// we (should) also have an array of backwards maps (warp->chip) for the above chips
+		psArray *backmaps  = psMetadataLookupPtr(&status, readout->analysis, PSWARP_ANALYSIS_BACKMAPS); // 
+		for (int i = 0; backmaps && (i < backmaps->n); i++) {
+		  psPlaneTransform *map = backmaps->data[i];		  
+		  psStringAppend (&keyword, "MPX_%04d", i);
+		  psStringAppend (&mapstring, "[%8.2f,%8.4f,%8.4f]", map->x->coeff[0][0], map->x->coeff[1][0], map->x->coeff[0][1]);
+		  psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "warp to image map", mapstring);
+		  psFree (keyword);
+		  psFree (mapstring);
+		  psStringAppend (&keyword, "MPY_%04d", i);
+		  psStringAppend (&mapstring, "[%8.2f,%8.4f,%8.4f]", map->y->coeff[0][0], map->y->coeff[1][0], map->y->coeff[0][1]);
+		  psMetadataAddStr(hdu->header, PS_LIST_TAIL, keyword, PS_META_REPLACE, "warp to image map", mapstring);
+		  psFree (keyword);
+		  psFree (mapstring);
+		}
+	    }
+
+	    if (fullImage) {
+		psList *inputCells = psMetadataLookupPtr (NULL, cell->analysis, "INPUT.CELLS");
+		if (!pmConceptsAverageCells(cell, inputCells, NULL, NULL, false)) {
+		    psError(psErrorCodeLast(), false, "Unable to average cell concepts.");
+		    psFree(view);
+		    return false;
+		}
+	    }
+
+	    // XXX Is this too ad-hoc?
+	    psRegion *trimsec = psMetadataLookupPtr(NULL, cell->concepts, "CELL.TRIMSEC"); ///< Trim section
+	    trimsec->x0 = trimsec->x1 = trimsec->y0 = trimsec->y1 = 0; ///< All pixels
+	}
+
+	// update astrometry headers
+	pmHDU *hdu = pmHDUFromChip(chip); ///< HDU for source
+	if (!hdu || !hdu->header) {
+	    psError(PM_ERR_PROG, false, "Unable to find header for output.");
+	    psFree(view);
+	    return false;
+	}
+
+	if (bilevelAstrometry) {
+	    if (!pmAstromWriteBilevelChip(hdu->header, chip, WCS_NONLIN_TOL)) {
+		psError(psErrorCodeLast(), false, "Unable to read bilevel chip astrometry for skycell.");
+		psFree(view);
+		return false;
+	    }
+	} else {
+	    // we use a default FPA pixel scale of 1.0
+	    if (!pmAstromWriteWCS(hdu->header, output, chip, WCS_NONLIN_TOL)) {
+		psError(psErrorCodeLast(), false, "Unable to read WCS astrometry for skycell.");
+		psFree(view);
+		return false;
+	    }
+	}
+    }
+
+    if (!psMetadataCopy(output->concepts, input->concepts)) {
+	psError(psErrorCodeLast(), false, "Unable to copy FPA concepts from input to output.");
+	return false;
+    }
+    
+    // Update ZP from the astrometry
+    if (fullImage) {
+	psMetadataItem *item = psMetadataLookup(output->concepts, "FPA.ZP");
+	item->data.F32 = psMetadataLookupF32(NULL, astrom->concepts, "FPA.ZP");
+    }
+
+    if (refcat) {
+      if ((output)&&(output->hdu)&&(output->hdu->header)) {
+	psMetadataAddStr(output->hdu->header, PS_LIST_TAIL, "PSREFCAT", PS_META_REPLACE,
+			 "Reference catalog used for calibration", refcat);
+      }
+    }
+    
+    // apply the bilevel astrometry elements to the target
+    if (bilevelAstrometry) {
+	pmHDU *phu = pmFPAviewThisPHU(view, output); ///< Astrometry PHU
+	if (!phu->header) {
+	    phu->header = psMetadataAlloc ();
+	}
+	if (!pmAstromWriteBilevelMosaic(phu->header, output, WCS_NONLIN_TOL)) {
+	    psError(psErrorCodeLast(), false, "Unable to write bilevel mosaic astrometry for skycell.");
+	    psFree(view);
+	    return false;
+	}
+    }
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpUpdateStatistics.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpUpdateStatistics.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpUpdateStatistics.c	(revision 41091)
@@ -0,0 +1,121 @@
+/** @file pswarpUpdateStatistics.c
+ *
+ *  @brief generate output statistics
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @date $Date: 2009-02-13 21:54:32 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "pswarp.h"
+
+// once the output fpa elements have been built, loop over the fpa and generate stats
+// for each readout
+bool pswarpUpdateStatistics (pmFPA *output, psMetadata *fpaStats, pmFPA *input, pmFPA *astrom, pmConfig *config)  {
+
+    if (!fpaStats) {
+	psLogMsg("pswarp", PS_LOG_INFO, "stats not requested, skipping");
+	return true;
+    }
+
+    // load the recipe
+    bool status = false;
+    psMetadata *recipe = psMetadataLookupPtr (&status, config->recipes, PSWARP_RECIPE);
+    if (!recipe) {
+        psError(PSWARP_ERR_CONFIG, false, "missing recipe %s", PSWARP_RECIPE);
+        return false;
+    }
+
+    // output mask bits
+    psImageMaskType maskValue = psMetadataLookupImageMask(&status, recipe, "MASK.OUTPUT");
+    psAssert (status, "MASK.OUTPUT was not defined");
+
+    int Nreadout = 0; // count the number of readouts with data
+
+    bool doMaskStats = psMetadataLookupBool(&status, recipe, "MASK.STATS"); 
+
+    pmFPAview *view = pmFPAviewAlloc(0);
+    
+    pmChip *chip;
+    while ((chip = pmFPAviewNextChip (view, output, 1)) != NULL) {
+        psTrace ("pswarp", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+	// use this in output metadata info (MD5 sums)
+	const char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME");
+
+	psMetadata *chipStats = psMetadataLookupPtr (&status, fpaStats, chipName);
+	if (!chipStats) {
+	  chipStats = psMetadataAlloc ();
+	  psMetadataAdd (fpaStats, PS_LIST_TAIL, chipName, PS_DATA_METADATA, "chip stats folder", chipStats);
+	  psFree (chipStats);
+	}
+
+        pmCell *cell;
+        while ((cell = pmFPAviewNextCell (view, output, 1)) != NULL) {
+            psTrace ("pswarp", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+	    // use this in output metadata info (MD5 sums)
+	    const char *cellName = psMetadataLookupStr(NULL, cell->concepts, "CELL.NAME");
+
+	    psMetadata *cellStats = psMetadataLookupPtr (&status, chipStats, cellName);
+	    if (!cellStats) {
+	      cellStats = psMetadataAlloc ();
+	      psMetadataAdd (chipStats, PS_LIST_TAIL, cellName, PS_DATA_METADATA, "cell stats folder", cellStats);
+	      psFree (cellStats);
+	    }
+
+            // process each of the readouts
+            pmReadout *readout;
+            while ((readout = pmFPAviewNextReadout(view, output, 1)) != NULL) {
+	      // skip empty output readouts
+                if (!readout->data_exists) {
+		  continue;
+		}
+		Nreadout ++;
+    
+		psString readoutName = NULL;
+		psStringAppend (&readoutName, "READOUT.%02d", view->readout);
+
+		psMetadata *readoutStats = psMetadataLookupPtr (&status, cellStats, readoutName);
+		if (!readoutStats) {
+		  readoutStats = psMetadataAlloc ();
+		  psMetadataAdd (cellStats, PS_LIST_TAIL, readoutName, PS_DATA_METADATA, "readout stats folder", readoutStats);
+		  psFree (readoutStats);
+		}
+
+		if (!pswarpPixelsLit(readout, readoutStats, config)) {
+		    psError(psErrorCodeLast(), false, "Unable to calculate pixel regions.");
+		    psFree(view);
+		    return false;
+		}
+
+		if (doMaskStats && !pswarpMaskStats(readout, readoutStats, config)) {
+		  psError(psErrorCodeLast(), false, "Unable to calculate mask stats.");
+		  psFree(view);
+		  return false;
+		}
+
+		psFree (readoutName);
+	    }
+	}
+    }
+
+    // Perform statistics on the output image (ppStatsFPA loops down the hierarchy)
+    pmFPAviewReset (view);
+    if (!ppStatsFPA(fpaStats, output, view, maskValue, config)) {
+	psWarning("Unable to perform statistics on warped image.");
+    }
+    psFree(view);
+
+    if (Nreadout == 0) {
+      psWarning("No overlap between input and skycell.");
+      if (fpaStats) {
+	psMetadataAddS32(fpaStats, PS_LIST_TAIL, "QUALITY", PS_META_REPLACE, "No overlap between input and skycell", PSWARP_ERR_NO_OVERLAP);
+      }
+    }      
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpVersion.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpVersion.c	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpVersion.c	(revision 41091)
@@ -0,0 +1,122 @@
+/** @file pswarpVersion.c
+ *
+ *  @brief
+ *
+ *  @ingroup pswarp
+ *
+ *  @author IfA
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-05 20:44:04 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "pswarp.h"
+#include "pswarpVersionDefinitions.h"
+
+#ifndef PSWARP_VERSION
+#error "PSWARP_VERSION is not set"
+#endif
+#ifndef PSWARP_BRANCH
+#error "PSWARP_BRANCH is not set"
+#endif
+#ifndef PSWARP_SOURCE
+#error "PSWARP_SOURCE is not set"
+#endif
+
+psString pswarpVersion(void)
+{
+    char *value = NULL;
+    psStringAppend(&value, "%s@%s", PSWARP_BRANCH, PSWARP_VERSION);
+    return value;
+}
+
+psString pswarpSource(void)
+{
+    return psStringCopy(PSWARP_SOURCE);
+}
+
+psString pswarpVersionLong(void)
+{
+    psString version = pswarpVersion();  // Version, to return
+    psString source = pswarpSource();    // Source
+
+    psStringPrepend(&version, "pswarp ");
+    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 pswarpVersionHeader(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, "pswarp 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);
+    ppStatsVersionHeader(header);
+
+    psString version = pswarpVersion(); // Software version
+    psString source  = pswarpSource();  // Software source
+
+    psMetadataAddStr(header, PS_LIST_TAIL, "WARP_V", PS_META_REPLACE, NULL, PSWARP_VERSION);
+    
+    psStringPrepend(&version, "pswarp version: ");
+    psStringPrepend(&source, "pswarp 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 pswarpVersionPrint(void)
+{
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psLogMsg("pswarp", PS_LOG_INFO, "pswarp at %s", timeString);
+    psFree(timeString);
+
+    psString pslib = psLibVersionLong();// psLib version
+    psString psmodules = psModulesVersionLong(); // psModules version
+    psString psphot = psphotVersionLong(); // psphot version
+    psString ppStats = ppStatsVersionLong(); // ppStats version
+    psString pswarp = pswarpVersionLong(); // pswarp version
+
+    psLogMsg("pswarp", PS_LOG_INFO, "%s", pslib);
+    psLogMsg("pswarp", PS_LOG_INFO, "%s", psmodules);
+    psLogMsg("pswarp", PS_LOG_INFO, "%s", psphot);
+    psLogMsg("pswarp", PS_LOG_INFO, "%s", ppStats);
+    psLogMsg("pswarp", PS_LOG_INFO, "%s", pswarp);
+
+    psFree(pslib);
+    psFree(psmodules);
+    psFree(psphot);
+    psFree(ppStats);
+    psFree(pswarp);
+
+    return;
+}
Index: /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpVersionDefinitions.h.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpVersionDefinitions.h.in	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/src/pswarpVersionDefinitions.h.in	(revision 41091)
@@ -0,0 +1,8 @@
+#ifndef PSWARP_VERSION_DEFINITIONS_H
+#define PSWARP_VERSION_DEFINITIONS_H
+
+#define PSWARP_VERSION @PSWARP_VERSION@ // SVN version
+#define PSWARP_BRANCH  @PSWARP_BRANCH@  // SVN branch
+#define PSWARP_SOURCE  @PSWARP_SOURCE@  // SVN source
+
+#endif
Index: /branches/ccl_branches/ipponly-20191108/pswarp/test/simple.image.op
===================================================================
--- /branches/ccl_branches/ipponly-20191108/pswarp/test/simple.image.op	(revision 41091)
+++ /branches/ccl_branches/ipponly-20191108/pswarp/test/simple.image.op	(revision 41091)
@@ -0,0 +1,56 @@
+
+$NX = 2048
+$NY = 2048
+
+macro go
+  mkref
+  mksrc
+  exec pswarp -file src.fits out ref.fits
+  exec rm -f src.fits out.fits ref.fits
+end
+
+macro mkref
+  mcreate ref $NX $NY
+  keyword ref CTYPE1 -w "RA---TAN"
+  keyword ref CTYPE2 -w "DEC--TAN"
+  keyword ref CRVAL1 -wf 0.0
+  keyword ref CRVAL2 -wf 0.0
+  keyword ref CRPIX1 -wf {$NX/2}
+  keyword ref CRPIX2 -wf {$NY/2}
+
+  keyword ref CDELT1 -wf {1/3600}
+  keyword ref CDELT2 -wf {1/3600}
+
+  keyword ref PC001001 -wf 1.0
+  keyword ref PC001002 -wf 0.0
+  keyword ref PC002001 -wf 0.0
+  keyword ref PC002002 -wf 1.0
+
+  wd ref ref.fits -bitpix -32 -bzero 0 -bscale 1
+end
+
+macro mksrc
+  mcreate src $NX $NY
+  for i 0 $NX 64
+    for j 0 $NY 64
+      zap src $i $j 1 1 -v 1
+    end
+  end
+
+  keyword src CTYPE1 -w "RA---TAN"
+  keyword src CTYPE2 -w "DEC--TAN"
+  keyword src CRVAL1 -wf 0.0
+  keyword src CRVAL2 -wf 0.0
+  keyword src CRPIX1 -wf {$NX/2}
+  keyword src CRPIX2 -wf {$NY/2}
+
+  keyword src CDELT1 -wf {1/3600}
+  keyword src CDELT2 -wf {1/3600}
+
+  keyword src PC001001 -wf {dcos(20)}
+  keyword src PC001002 -wf {-1*dsin(20)}
+  keyword src PC002001 -wf {dsin(20)}
+  keyword src PC002002 -wf {dcos(20)}
+
+  wd src src.fits -bitpix -32 -bzero 0 -bscale 1
+end
