Index: /branches/eam_branches/eam_branch_20090312/psastro/Doxyfile.in
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/Doxyfile.in	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/Doxyfile.in	(revision 23321)
@@ -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           = psastro
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER         = ipp-2.7.dev
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = ./docs
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of 
+# source files, where putting all generated files in the same directory would 
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, 
+# Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hungarian, 
+# Italian, Japanese, Japanese-en (Japanese with English messages), Korean, 
+# Korean-en, Lithuanian, Norwegian, Polish, Portuguese, Romanian, Russian, 
+# Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is 
+# used as the annotated text. Otherwise, the brief description is used as-is. 
+# If left blank, the following values are used ("$name" is automatically 
+# replaced with the name of the entity): "The $name class" "The $name widget" 
+# "The $name file" "is" "provides" "specifies" "contains" 
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
+# inherited members of a class in the documentation of that class as if those 
+# members were ordinary class members. Constructors, destructors and assignment 
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful is your file systems 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like regular Qt-style comments 
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will 
+# interpret the first line (until the first dot) of a Qt-style 
+# comment as the brief description. If set to NO, the comments 
+# will behave just like regular Qt-style comments (thus requiring 
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen 
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member 
+# documentation.
+
+DETAILS_AT_TOP         = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
+# a new page for each member. If set to NO, the documentation of a member will 
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
+# sources only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java 
+# sources only. Doxygen will then generate output that is more tailored for Java. 
+# For instance, namespaces will be presented as packages, qualified scopes 
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to 
+# include (a tag file for) the STL sources as input, then you should 
+# set this tag to YES in order to let doxygen match functions declarations and 
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. 
+# func(std::string) {}). This also make the inheritance and collaboration 
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. 
+# Doxygen will parse them like normal C++ but will assume all classes use public 
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct (or union) is 
+# documented as struct with the name of the typedef. So 
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct 
+# with name TypeT. When disabled the typedef will appear as a member of a file, 
+# namespace, or class. And the struct will be named TypeS. This can typically 
+# be useful for C code where the coding convention is that all structs are 
+# typedef'ed and only the typedef is referenced never the struct's name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local 
+# methods, which are defined in the implementation section but not in 
+# the interface are included in the documentation. 
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be extracted 
+# and appear in the documentation as a namespace called 'anonymous_namespace{file}', 
+# where file will be replaced with the base name of the file that contains the anonymous 
+# namespace. By default anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories 
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy 
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
+# doxygen should invoke to get the current version for each file (typically from the 
+# version control system). Doxygen will invoke the program by executing (via 
+# popen()) the command <command> <input-file>, where <command> is the value of 
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
+# provided by doxygen. Whatever the program writes to standard output 
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for 
+# functions that are documented, but have no documentation for their parameters 
+# or return value. If set to NO (the default) doxygen will only warn about 
+# wrong or incomplete parameter documentation, but not about the absence of 
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text. Optionally the format may contain 
+# $version, which will be replaced by the version of the file (if it could 
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text "
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT                  = ./src
+
+# This tag can be used to specify the character encoding of the source files that 
+# doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default 
+# input encoding. Doxygen uses libiconv (or the iconv built into libc) for the transcoding. 
+# See http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx 
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS          = *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                = 
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
+# directories that are symbolic links (a Unix filesystem feature) are excluded 
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories. Note that the wildcards are matched 
+# against the file with absolute path, so to exclude all test directories 
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       = 
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names 
+# (namespaces, classes, functions, etc.) that should be excluded from the output. 
+# The symbol name can be a fully qualified name, a word, or if the wildcard * is used, 
+# a substring. Examples: ANamespace, AClass, AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           = 
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = 
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis.  Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match.  The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
+# is applied to all files.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO. If you have enabled CALL_GRAPH or CALLER_GRAPH 
+# then you must also enable this option. If you don't then doxygen will produce 
+# a warning and turn it on anyway
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default) 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default) 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.  Otherwise they will link to the documentstion.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code 
+# will point to the HTML generated by the htags(1) tool instead of doxygen 
+# built-in source browser. The htags tool is part of GNU's global source 
+# tagging system (see http://www.gnu.org/software/global/global.html). You 
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        = 
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
+# files or namespaces will be aligned in HTML using tables. If set to 
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML 
+# documentation will contain sections that can be hidden and shown after the 
+# page has loaded. For this to work a browser that supports 
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox 
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA             = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD                = 
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader.  This is useful 
+# if you want to understand what is going on.  On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor   
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED             = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse 
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#   TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#   TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
+# or super classes. Setting the tag to NO turns the diagrams off. Note that 
+# this option is superseded by the HAVE_DOT option below. This is only a 
+# fallback. It is recommended to install and use dot, since it yields more 
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc 
+# command. Doxygen will then run the mscgen tool (see http://www.mcternan.me.uk/mscgen/) to 
+# produce the chart and insert it in the documentation. The MSCGEN_PATH tag allows you to 
+# specify the directory where the mscgen tool resides. If left empty the tool is assumed to 
+# be found in the default search path.
+
+MSCGEN_PATH            = 
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will 
+# generate a call dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable call graphs for selected 
+# functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will 
+# generate a caller dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable caller graphs for selected 
+# functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES 
+# then doxygen will show the dependencies a directory has on other directories 
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of 
+# nodes that will be shown in the graph. If the number of nodes in a graph 
+# becomes larger than this value, doxygen will truncate the graph, which is 
+# visualized by representing a node as a red box. Note that doxygen if the number 
+# of direct children of the root node in a graph is already larger than 
+# MAX_DOT_GRAPH_NOTES then the graph will not be shown at all. Also note 
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes 
+# that lay further from the root node will be omitted. Note that setting this 
+# option to 1 or 2 may greatly reduce the computation time needed for large 
+# code bases. Also note that the size of a graph can be further restricted by 
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
+# background. This is disabled by default, which results in a white background. 
+# Warning: Depending on the platform used, enabling this option may lead to 
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to 
+# read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
+# files in one run (i.e. multiple -o and -T options on the command line). This 
+# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine   
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
Index: /branches/eam_branches/eam_branch_20090312/psastro/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/Makefile.am	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/Makefile.am	(revision 23321)
@@ -0,0 +1,10 @@
+SUBDIRS = src
+
+CLEANFILES = *.pyc *~ core core.*
+
+pkgconfigdir = $(libdir)/pkgconfig
+pkgconfig_DATA= psastro.pc
+
+EXTRA_DIST = \
+	psastro.pc.in \
+	autogen.sh
Index: /branches/eam_branches/eam_branch_20090312/psastro/autogen.sh
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/autogen.sh	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/autogen.sh	(revision 23321)
@@ -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=psastro
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOLIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOLIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE  failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/eam_branches/eam_branch_20090312/psastro/configure.ac
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/configure.ac	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/configure.ac	(revision 23321)
@@ -0,0 +1,205 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.61)
+
+AC_INIT([psastro], [0.9.0], [ipp-support@ifa.hawaii.edu])
+AC_CONFIG_SRCDIR([src])
+
+AM_INIT_AUTOMAKE([1.6 foreign dist-bzip2])
+AM_CONFIG_HEADER([src/config.h])
+AM_MAINTAINER_MODE
+
+IPP_STDCFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC_C99
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE
+
+dnl ------------------------------------------------------------
+
+AC_PATH_PROG([ERRORCODES], [psParseErrorCodes], [missing])
+if test "$ERRORCODES" = "missing" ; then
+  AC_MSG_ERROR([psParseErrorCodes is required])
+fi
+
+dnl ------------------ kapa,libkapa options -------------------------
+dnl -- libkapa implies the requirement for libpng, libjpeg as well --
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+dnl test for command-line options: use ohana-config if not supplied
+KAPA_CFLAGS_CONFIG="true"
+KAPA_LIBS_CONFIG="true"
+AC_ARG_WITH(kapa,
+[AS_HELP_STRING(--with-kapa=DIR,Specify location of libkapa)],
+[KAPA_CFLAGS="-I$withval/include" KAPA_LIBS="-L$withval/lib" 
+ KAPA_CFLAGS_CONFIG="false"       KAPA_LIBS_CONFIG="false"])
+AC_ARG_WITH(kapa-include,
+[AS_HELP_STRING(--with-kapa-include=DIR,Specify libkapa include directory.)],
+[KAPA_CFLAGS="-I$withval" KAPA_CFLAGS_CONFIG="false"])
+AC_ARG_WITH(kapa-lib,
+[AS_HELP_STRING(--with-kapa-lib=DIR,Specify libkapa library directory.)],
+[KAPA_LIBS="-L$withval" KAPA_LIBS_CONFIG="false"])
+
+echo "KAPA_CFLAGS_CONFIG: $KAPA_CFLAGS_CONFIG"
+echo "KAPA_LIBS_CONFIG: $KAPA_LIBS_CONFIG"
+echo "KAPA_CFLAGS: $KAPA_CFLAGS"
+echo "KAPA_LIBS: $KAPA_LIBS"
+
+dnl HAVE_KAPA is set to false if any of the tests fail
+HAVE_KAPA="true"
+AC_MSG_NOTICE([checking for libkapa])
+if test "$KAPA_CFLAGS_CONFIG" = "true" -o "$KAPA_LIBS_CONFIG" = "true"; then
+  AC_MSG_NOTICE([kapa info supplied by ohana-config])
+  KAPA_CONFIG=`which ohana-config`
+  AC_CHECK_FILE($KAPA_CONFIG,[],
+    [HAVE_KAPA="false"; AC_MSG_WARN([libkapa is not found: output plots disabled.  Obtain libkapa at http://kiawe.ifa.hawaii.edu/Elixir/Ohana or use --with-kapa to specify location])])
+  
+  echo "HAVE_KAPA: $HAVE_KAPA"
+  echo "KAPA_CFLAGS_CONFIG: $KAPA_CFLAGS_CONFIG"
+
+  if test "$HAVE_KAPA" = "true" -a "$KAPA_CFLAGS_CONFIG" = "true" ; then
+   AC_MSG_NOTICE([libkapa cflags info supplied by ohana-config])
+   AC_MSG_CHECKING([libkapa cflags])
+   KAPA_CFLAGS="`${KAPA_CONFIG} --cflags`"
+   AC_MSG_RESULT([${KAPA_CFLAGS}])
+  fi
+
+  if test "$HAVE_KAPA" = "true" -a "$KAPA_LIBS_CONFIG" = "true" ; then
+   AC_MSG_NOTICE([libkapa ldflags info supplied by ohana-config])
+   AC_MSG_CHECKING([libkapa ldflags])
+   KAPA_LIBS="`${KAPA_CONFIG} --libs` -lX11"
+   AC_MSG_RESULT([${KAPA_LIBS}])
+  fi
+fi
+
+if test "$HAVE_KAPA" = "true" ; then
+ AC_MSG_NOTICE([libkapa supplied])
+ PSASTRO_CFLAGS="${PSASTRO_CFLAGS} ${KAPA_CFLAGS}"
+ PSASTRO_LIBS="${PSASTRO_LIBS} ${KAPA_LIBS}"
+else
+ AC_MSG_NOTICE([libkapa ignored])
+fi
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ libjpeg options ---------------------
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+AC_ARG_WITH(jpeg,
+[AS_HELP_STRING(--with-jpeg=DIR,Specify location of libjpeg.)],
+[JPEG_CFLAGS="-I$withval/include"
+ JPEG_LDFLAGS="-L$withval/lib"])
+AC_ARG_WITH(jpeg-include,
+[AS_HELP_STRING(--with-jpeg-include=DIR,Specify libjpeg include directory.)],
+[JPEG_CFLAGS="-I$withval"])
+AC_ARG_WITH(jpeg-lib,
+[AS_HELP_STRING(--with-jpeg-lib=DIR,Specify libjpeg library directory.)],
+[JPEG_LDFLAGS="-L$withval"])
+
+CFLAGS="${CFLAGS} ${JPEG_CFLAGS}"
+CPPFLAGS=${CFLAGS}
+LDFLAGS="${LDFLAGS} ${JPEG_LDFLAGS}"
+
+AC_CHECK_HEADERS([jpeglib.h],
+  [PSASTRO_CFLAGS="$PSASTRO_CFLAGS $JPEG_CFLAGS" AC_SUBST(JPEG_CFLAGS)],
+  [HAVE_KAPA=false; AC_MSG_WARN([libjpeg headers not found: output plots disabled.  Obtain libjpeg from http://www.ijg.org/ or use --with-jpeg to specify location.])]
+)
+
+AC_CHECK_LIB(jpeg,jpeg_CreateCompress,
+  [PSASTRO_LIBS="$PSASTRO_LIBS $JPEG_LDFLAGS -ljpeg"],
+  [HAVE_KAPA=false; AC_MSG_WARN([libjpeg library not found: output plots disabled.  Obtain libjpeg from http://www.ijg.org/ or use --with-jpeg to specify location.])]
+)
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ libpng options ---------------------
+
+dnl save LIBS/CFLAGS/LDFLAGS
+TMP_LIBS=${LIBS}
+TMP_CFLAGS=${CFLAGS}
+TMP_LDFLAGS=${LDFLAGS}
+TMP_CPPFLAGS=${CPPFLAGS}
+
+AC_ARG_WITH(png,
+[AS_HELP_STRING(--with-png=DIR,Specify location of libpng.)],
+[PNG_CFLAGS="-I$withval/include"
+ PNG_LDFLAGS="-L$withval/lib"])
+AC_ARG_WITH(png-include,
+[AS_HELP_STRING(--with-png-include=DIR,Specify libpng include directory.)],
+[PNG_CFLAGS="-I$withval"])
+AC_ARG_WITH(png-lib,
+[AS_HELP_STRING(--with-png-lib=DIR,Specify libpng library directory.)],
+[PNG_LDFLAGS="-L$withval"])
+
+CFLAGS="${CFLAGS} ${PNG_CFLAGS}"
+CPPFLAGS=${CFLAGS}
+LDFLAGS="${LDFLAGS} ${PNG_LDFLAGS}"
+
+AC_CHECK_HEADERS([png.h],
+  [PSASTRO_CFLAGS="$PSASTRO_CFLAGS $PNG_CFLAGS" AC_SUBST(PNG_CFLAGS)],
+  [HAVE_KAPA=false; AC_MSG_WARN([libpng headers not found: output plots disabled.  Obtain libpng from http://www.ijg.org/ or use --with-png to specify location.])]
+)
+
+AC_CHECK_LIB(png,png_init_io,
+  [PSASTRO_LIBS="$PSASTRO_LIBS $PNG_LDFLAGS -lpng"],
+  [HAVE_KAPA=false; AC_MSG_WARN([libpng library not found: output plots disabled.  Obtain libpng from http://www.ijg.org/ or use --with-png to specify location.])]
+)
+
+dnl restore the CFLAGS/LDFLAGS
+LIBS=${TMP_LIBS}
+CFLAGS=${TMP_CFLAGS}
+LDFLAGS=${TMP_LDFLAGS}
+CPPFLAGS=${TMP_CPPFLAGS}
+
+dnl ------------------ use kapa or not? ---------------------
+
+if test "$HAVE_KAPA" == "true" ; then
+  AC_MSG_RESULT([including plotting functions])
+  AC_DEFINE([HAVE_KAPA],[1],[enable use of libkapa])
+else
+  AC_MSG_RESULT([skipping plotting functions])
+  AC_DEFINE([HAVE_KAPA],[0],[disable use of libkapa])
+fi
+
+dnl ------------- psLib, psModules ---------------
+PKG_CHECK_MODULES([PSLIB],    [pslib >= 1.0.0])
+PKG_CHECK_MODULES([PSMODULE], [psmodules >= 1.0.0])
+PKG_CHECK_MODULES([PPSTATS],  [ppStats >= 1.0.0]) 
+
+dnl Set CFLAGS for build
+IPP_STDOPTS
+CFLAGS="${CFLAGS=} -Wall -Werror"
+echo "PSASTRO_CFLAGS: $PSASTRO_CFLAGS"
+echo "PSASTRO_LIBS: $PSASTRO_LIBS"
+
+AC_SUBST([PSASTRO_CFLAGS])
+AC_SUBST([PSASTRO_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  psastro.pc
+  Doxyfile
+])
+
+AC_OUTPUT
Index: /branches/eam_branches/eam_branch_20090312/psastro/doc/mktest.txt
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/doc/mktest.txt	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/doc/mktest.txt	(revision 23321)
@@ -0,0 +1,31 @@
+
+the program, psastro-mktest, will generate a set of fake test data for
+psastro.  the config file 'psastro-mktest.conf' gives an example setup
+for this program.  It generates 4 output files:
+
+- cmpfile : this is a basic elixir-style cmp file (stars plus header)
+  which can be taken as input to psastro
+
+- catfile : this is a basic text reference catalog, which the current
+  psastro loads as a reference
+
+- rawfile : this is a text listing of all astrometry stages for the
+  generated stars: readout, cell, chip, fpa, tpa, sky, with the last
+  two columns being the magnitudes.  this file is generated by
+  applying the transformations starting at the readout and going
+  step-by-step to the sky.
+
+- reffile : this is the inverse of the preceeding file, with the
+  resulting sources transformed from the sky coordinates down
+  step-by-step to the readout pixels.  If the transformations are
+  functioning, these two files (rawfile & reffile) should be identical
+  within floating point errors.
+
+examples:
+
+build a fake dataset
+# psastro-mktest doc/psastro-mktest.conf test.cmp test.cat test.raw test.ref
+
+run psastrom on the fake data:
+# psastro doc/psastro.conf test.cmp test.dat
+
Index: /branches/eam_branches/eam_branch_20090312/psastro/doc/notes.txt
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/doc/notes.txt	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/doc/notes.txt	(revision 23321)
@@ -0,0 +1,39 @@
+
+2006.12.26
+
+ work still to be done for psastro:
+
+ - test suite
+   - create a DVO database from a fake population
+   - create fake images with range of errors drawn from DVO
+
+ - 
+
+
+we have a few different astrometry circumstances for our complete
+image/chip hierarchy structure:
+
+- ChipAstrom with one readout, one cell per chip:
+
+  whether or not there is more than one chip, we need to have two
+  stages
+  
+    - in the first stage, the per-chip results are applied to the
+      chip.toFPA parameter set.  
+
+    - in the second stage, the chip results are collectively used to
+      modify the fpa.toSky terms.
+
+      - the average offsets of the chip positions relative to starting
+	coordinates are used to calculate a single average offset to
+	the boresite. 
+
+      - the average rotations of the chips relative to their starting
+        rotations are used to calculate a single average rotation of
+        the boresite
+
+
+- ChipAstrom with more than one readout and/or cell:
+
+  - match all stars up relative to first readout?
+   
Index: /branches/eam_branches/eam_branch_20090312/psastro/doc/psastro-mktest.conf
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/doc/psastro-mktest.conf	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/doc/psastro-mktest.conf	(revision 23321)
@@ -0,0 +1,28 @@
+
+NX            S32  2000    	 # chip dimensions
+NY            S32  2000    	 # chip dimensions
+
+NSTARS        S32  20    	 # number of fake stars to generate
+
+# the current test output file only has the basic WCS info.  
+READOUT.COL.0    F32  0		 # cell coord for readout (0,0)
+READOUT.ROW.0    F32  0		 # cell coord for readout (0,0)
+READOUT.COL.BIN  F32  1		 # readout col binning factor
+READOUT.ROW.BIN  F32  1		 # readout row binning factor
+
+CELL.COL.0    	 F32  0		 # chip coord for cell (0,0)
+CELL.ROW.0    	 F32  0		 # chip coord for cell (0,0)
+
+CHIP.COL.0    	 F32  0		 # FPA coord for chip (0,0)
+CHIP.ROW.0    	 F32  0		 # FPA coord for chip (0,0)
+CHIP.THETA    	 F32  0	         # cw rotation from chip to FPA
+CHIP.PARITY.X 	 F32  1		 # x flip 
+CHIP.PARITY.Y 	 F32  1		 # y flip 
+
+FPA.THETA     	 F32  0		 # fpa row offset
+FPA.COL.0  	 F32  0		 # TPA coord for FPA (0,0)
+FPA.ROW.0  	 F32  0		 # TPA coord for FPA (0,0)
+
+PLATE.SCALE	 F32  1	         # plate scale in arcsec/pixel
+RA		 F32  10	 # celestial coord for TPA (0,0)
+DEC     	 F32  20	 # celestial coord for TPA (0,0)
Index: /branches/eam_branches/eam_branch_20090312/psastro/doc/psastro.conf
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/doc/psastro.conf	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/doc/psastro.conf	(revision 23321)
@@ -0,0 +1,17 @@
+
+PSASTRO.MATCH.RADIUS 	F32 2.0    # fitting radius in pixels
+
+PSASTRO.CHIP.NX      	S32 1      # chip-to-fpa tranformation order
+PSASTRO.CHIP.NY	     	S32 1      # chip-to-fpa tranformation order
+
+PSASTRO.CHIP.NSIGMA  	F32 3.0    # Nsigma clip for matched-fit 
+PSASTRO.CHIP.NITER   	S32 3      # number of clipping interations for matched-fit 
+
+PSASTRO.GRID.OFFSET  	F32 100.0  # maximum allowed offset for grid match
+PSASTRO.GRID.SCALE   	F32  50.0  # binning scale for grid match
+
+PSASTRO.GRID.MIN.ANGLE  F32   0.0  # starting angle
+PSASTRO.GRID.MAX.ANGLE  F32   0.0  # ending angle
+PSASTRO.GRID.DEL.ANGLE  F32   0.5  # steps between angle attempts
+
+PSASTRO.STARS.MAX       S32 300    # use no more than this number of sources
Index: /branches/eam_branches/eam_branch_20090312/psastro/psastro.pc.in
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/psastro.pc.in	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/psastro.pc.in	(revision 23321)
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libpsastro
+Description: Pan-STARRS Photometry Library
+Version: @VERSION@
+Requires: pslib psmodules
+Libs: -L${libdir} -lpsastro
+Libs.private: @PSASTRO_LIBS@
+Cflags: -I${includedir} @PSASTRO_CFLAGS@
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/Makefile.am
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/Makefile.am	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/Makefile.am	(revision 23321)
@@ -0,0 +1,116 @@
+lib_LTLIBRARIES = libpsastro.la
+
+libpsastro_la_CFLAGS = $(PSASTRO_CFLAGS) $(PPSTATS_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS) -DPSASTRO_VERSION=$(SVN_VERSION) -DPSASTRO_BRANCH=$(SVN_BRANCH) -DPSASTRO_SOURCE=$(SVN_SOURCE)
+libpsastro_la_LDFLAGS = $(PSASTRO_LIBS) $(PPSTATS_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+
+# Force recompilation of psastroVersion.c, since it gets the version information
+# can we do this with dependency info?
+# psastroVersion.c: FORCE
+# touch psastroVersion.c
+# FORCE: ;
+
+bin_PROGRAMS = psastro psastroModel psastroModelFit gpcModel
+
+psastro_CFLAGS = $(PSASTRO_CFLAGS) $(PPSTATS_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+psastro_LDFLAGS = $(PSASTRO_LIBS) $(PPSTATS_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+psastro_LDADD = libpsastro.la
+
+psastroModel_CFLAGS = $(PSASTRO_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+psastroModel_LDFLAGS = $(PSASTRO_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+psastroModel_LDADD = libpsastro.la
+
+psastroModelFit_CFLAGS = $(PSASTRO_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+psastroModelFit_LDFLAGS = $(PSASTRO_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+psastroModelFit_LDADD = libpsastro.la
+
+gpcModel_CFLAGS = $(PSASTRO_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+gpcModel_LDFLAGS = $(PSASTRO_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+gpcModel_LDADD = libpsastro.la
+
+psastro_SOURCES = \
+	psastro.c		    \
+        psastroArguments.c	    \
+	psastroParseCamera.c   	    \
+	psastroDataLoad.c           \
+	psastroDataSave.c           \
+	psastroMetadataStats.c      \
+	psastroCleanup.c
+
+psastroModel_SOURCES = \
+	psastroModel.c		    \
+        psastroModelArguments.c	    \
+	psastroModelParseCamera.c   \
+	psastroModelDataLoad.c      \
+	psastroModelAnalysis.c      \
+	psastroModelBoresite.c      \
+	psastroModelFitBoresite.c   \
+	psastroModelAdjust.c        \
+	psastroModelDataSave.c      \
+	psastroCleanup.c
+
+psastroModelFit_SOURCES = \
+	psastroModelFit.c \
+	psastroModelBoresite.c      \
+	psastroModelFitBoresite.c
+
+gpcModel_SOURCES = gpcModel.c
+
+libpsastro_la_SOURCES = \
+	psastroErrorCodes.c         \
+	psastroVersion.c            \
+	psastroDefineFiles.c        \
+	psastroAnalysis.c           \
+	psastroMaskUpdates.c        \
+	psastroAstromGuess.c        \
+	psastroLoadRefstars.c       \
+	psastroChooseRefstars.c     \
+	psastroConvert.c	    \
+	psastroChipAstrom.c         \
+	psastroOneChipGrid.c	    \
+	psastroOneChipFit.c	    \
+	psastroRemoveClumps.c	    \
+	psastroUtils.c	       	    \
+	psastroTestFuncs.c          \
+	psastroLuminosityFunction.c \
+	psastroRefstarSubset.c      \
+	psastroFixChips.c           \
+	psastroFixChipsTest.c       \
+	psastroUseModel.c           \
+	psastroMosaicAstrom.c       \
+	psastroMosaicDistortion.c   \
+	psastroMosaicFPtoTP.c       \
+	psastroMosaicGradients.c    \
+	psastroMosaicCorrectDistortion.c   \
+	psastroMosaicChipAstrom.c   \
+	psastroMosaicOneChip.c      \
+	psastroMosaicSetAstrom.c    \
+	psastroMosaicSetMatch.c     \
+	psastroZeroPoint.c    	    \
+	psastroDemoDump.c           \
+	psastroDemoPlot.c
+
+include_HEADERS = \
+	psastro.h \
+	psastroErrorCodes.h
+
+noinst_HEADERS = \
+	psastroInternal.h \
+	psastroStandAlone.h
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
+### Error codes.
+BUILT_SOURCES = psastroErrorCodes.h psastroErrorCodes.c
+CLEANFILES = psastroErrorCodes.h psastroErrorCodes.c
+EXTRA_DIST = psastroErrorCodes.dat psastroErrorCodes.h.in psastroErrorCodes.c.in
+
+psastroErrorCodes.h : psastroErrorCodes.dat psastroErrorCodes.h.in
+	$(ERRORCODES) --data=psastroErrorCodes.dat --outdir=. psastroErrorCodes.h
+
+psastroErrorCodes.c : psastroErrorCodes.dat psastroErrorCodes.c.in psastroErrorCodes.h
+	$(ERRORCODES) --data=psastroErrorCodes.dat --outdir=. psastroErrorCodes.c
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/gpcModel.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/gpcModel.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/gpcModel.c	(revision 23321)
@@ -0,0 +1,178 @@
+/** @file gpcModel.c
+ *
+ *  @brief
+ *
+ *  @ingroup gpcModel
+ *
+ *  @author IfA
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroStandAlone.h"
+
+/**
+ * generate model for gpc based on input model for all but chips
+ * USAGE: gpcModel (input) (output)
+ */
+int main (int argc, char **argv) {
+
+    if (argc != 3) {
+	fprintf (stderr, "USAGE: gpcModel (input) (output)\n");
+	exit (2);
+    }
+
+    psFits *input = psFitsOpen (argv[1], "r");
+    psFits *output = psFitsOpen (argv[2], "w");
+
+    { // PHU
+	psMetadata *header = psFitsReadHeader (NULL, input);
+	psFitsWriteBlank (output, header, "");
+    }
+
+    { // CHIPS
+
+	// read initial chips
+	if (!psFitsMoveExtName (input, "CHIPS")) {
+	    psError(PS_ERR_IO, false, "missing CHIPS extension in astrometry table\n");
+	    return false;
+	}
+	psArray *chips = psFitsReadTable (input);
+	if (!chips) psAbort("cannot read chips");
+	fprintf (stderr, "read %ld rows from CHIPS\n", chips->n);
+
+	psMetadata *header = psMetadataAlloc();
+	psMetadataAddStr(header, PS_LIST_TAIL, "COORD",    PS_META_REPLACE, "name of this layer",	"CHIPS");
+	psMetadataAddStr(header, PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",     	"FOCAL_PLANE");
+	psMetadataAddStr(header, PS_LIST_TAIL, "BOUNDARY", PS_META_REPLACE, "validity region",   	"RECTANGLE");
+	psMetadataAddStr(header, PS_LIST_TAIL, "TRANSFRM", PS_META_REPLACE, "mapping to parent", 	"POLYNOMIAL");
+
+	float coeff[2][2];
+	char coeffMask[2][2];
+	coeff[0][0] = 0.0;
+	coeff[1][0] = 1.0;
+	coeff[0][1] = 0.0;
+	coeff[1][1] = 0.0;
+
+	coeffMask[0][0] = 0;
+	coeffMask[1][0] = 0;
+	coeffMask[0][1] = 0;
+	coeffMask[1][1] = 1;
+
+	psArray *table = psArrayAllocEmpty (1);
+	for (int i = 0; i < 8; i++) {
+	    for (int j = 0; j < 8; j++) {
+		if ((i == 0) && (j == 0)) continue;
+		if ((i == 0) && (j == 7)) continue;
+		if ((i == 7) && (j == 0)) continue;
+		if ((i == 7) && (j == 7)) continue;
+
+		float Xo;
+		float Yo;
+		if (i < 4) {
+		    Xo = (3 - i)*4970.0 +  60.0;
+		    Yo = (3 - j)*5133.0 + 125.0;
+		    coeff[1][0] = +1.0;
+		} else {
+		    Xo = (4 - i)*4970.0 -  60.0;
+		    Yo = (4 - j)*5133.0 - 150.0;
+		    coeff[1][0] = -1.0;
+		}	    
+
+		for (int ix = 0; ix < 2; ix++) {
+		    for (int iy = 0; iy < 2; iy++) {
+
+			psMetadata *row = psMetadataAlloc ();
+		
+			char chipname[80];
+			sprintf (chipname, "XY%d%d", i, j);
+			psMetadataAddStr(row,    PS_LIST_TAIL, "SEGMENT",  PS_META_REPLACE, "name of this segment", chipname);
+			psMetadataAddStr(row,    PS_LIST_TAIL, "PARENT",   PS_META_REPLACE, "next layer up",        "FOCAL_PLANE");
+			psMetadataAddF32(row,    PS_LIST_TAIL, "MINX",     PS_META_REPLACE, "range", 0.0);
+			psMetadataAddF32(row,    PS_LIST_TAIL, "MAXX",     PS_META_REPLACE, "range", 4846.0);
+			psMetadataAddF32(row,    PS_LIST_TAIL, "MINY",     PS_META_REPLACE, "range", 0.0);
+			psMetadataAddF32(row,    PS_LIST_TAIL, "MAXY",     PS_META_REPLACE, "range", 4868.0);
+
+			psMetadataAddS32(row,    PS_LIST_TAIL, "XORDER",   PS_META_REPLACE, "", ix);
+			psMetadataAddS32(row,    PS_LIST_TAIL, "YORDER",   PS_META_REPLACE, "", iy);
+			psMetadataAddS32(row,    PS_LIST_TAIL, "NXORDER",  PS_META_REPLACE, "", 1);
+			psMetadataAddS32(row,    PS_LIST_TAIL, "NYORDER",  PS_META_REPLACE, "", 1);
+			if ((ix == 0) && (iy == 0)) {
+			    psMetadataAddF32(row,    PS_LIST_TAIL, "POLY_X",   PS_META_REPLACE, "", Xo);
+			    psMetadataAddF32(row,    PS_LIST_TAIL, "POLY_Y",   PS_META_REPLACE, "", Yo);
+			} else {
+			    psMetadataAddF32(row,    PS_LIST_TAIL, "POLY_X",   PS_META_REPLACE, "", coeff[ix][iy]);
+			    psMetadataAddF32(row,    PS_LIST_TAIL, "POLY_Y",   PS_META_REPLACE, "", coeff[iy][ix]);
+			}
+			psMetadataAddF32(row,    PS_LIST_TAIL, "ERROR_X",  PS_META_REPLACE, "", 0.0);
+			psMetadataAddF32(row,    PS_LIST_TAIL, "ERROR_Y",  PS_META_REPLACE, "", 0.0);
+			psMetadataAddU8 (row,    PS_LIST_TAIL, "MASK_X",   PS_META_REPLACE, "", coeffMask[ix][iy]);
+			psMetadataAddU8 (row,    PS_LIST_TAIL, "MASK_Y",   PS_META_REPLACE, "", coeffMask[ix][iy]);
+			psArrayAdd (table, 100, row);
+			psFree (row);
+		    }
+		}
+	    }
+	}
+    
+	if (!psFitsWriteTable (output, header, table, "CHIPS")) {
+	    psError(PS_ERR_IO, false, "writing sky data\n");
+	    psFree(table);
+	    return false;
+	}
+    }
+
+    { // FP
+	if (!psFitsMoveExtName (input, "FP")) {
+	    psError(PS_ERR_IO, false, "missing FP extension in astrometry table\n");
+	    return false;
+	}
+	psMetadata *header = psFitsReadHeader (NULL, input);
+	psArray *table = psFitsReadTable (input);
+	if (!table) psAbort("cannot read fp");
+	fprintf (stderr, "read %ld rows from FP\n", table->n);
+
+	if (!psFitsWriteTable (output, header, table, "FP")) {
+	    psError(PS_ERR_IO, false, "writing sky data\n");
+	    psFree(table);
+	    return false;
+	}
+    }
+
+    { // TP
+	if (!psFitsMoveExtName (input, "TP")) {
+	    psError(PS_ERR_IO, false, "missing TP extension in astrometry table\n");
+	    return false;
+	}
+	psMetadata *header = psFitsReadHeader (NULL, input);
+	psArray *table = psFitsReadTable (input);
+	if (!table) psAbort("cannot read tp");
+	fprintf (stderr, "read %ld rows from TP\n", table->n);
+	if (!psFitsWriteTable (output, header, table, "TP")) {
+	    psError(PS_ERR_IO, false, "writing sky data\n");
+	    psFree(table);
+	    return false;
+	}
+    }
+
+    { // SKY
+	if (!psFitsMoveExtName (input, "SKY")) {
+	    psError(PS_ERR_IO, false, "missing SKY extension in astrometry table\n");
+	    return false;
+	}
+	psMetadata *header = psFitsReadHeader (NULL, input);
+	psArray *sky = psFitsReadTable (input);
+	if (!sky) psAbort("cannot read sky");
+	fprintf (stderr, "read %ld rows from SKY\n", sky->n);
+	if (!psFitsWriteTable (output, header, sky, "SKY")) {
+	    psError(PS_ERR_IO, false, "writing sky data\n");
+	    psFree(sky);
+	    return false;
+	}
+    }
+
+    psFitsClose (input);
+    psFitsClose (output);
+    exit (0);
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastro.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastro.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastro.c	(revision 23321)
@@ -0,0 +1,67 @@
+/** @file psastro.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.28 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroStandAlone.h"
+
+static void usage (void) {
+    fprintf (stderr, "USAGE: psastro [-file image(s)] [-list imagelist] (output)\n");
+    exit (2);
+}
+
+int main (int argc, char **argv) {
+
+    pmConfig *config = NULL;
+
+    psTimerStart ("complete");
+
+    psastroErrorRegister();              // register our error codes/messages
+
+    // model inits are needed in pmSourceIO
+    // models defined in psphot/src/models are not available in psastro
+    pmModelClassInit ();
+
+    // load configuration information
+    config = psastroArguments (argc, argv);
+    if (!config) usage ();
+
+    psastroVersionPrint();
+
+    // load identify the data sources
+    if (!psastroParseCamera (config)) {
+        psErrorStackPrint(stderr, "error setting up the camera\n");
+        exit (1);
+    }
+
+    // load the raw detection data (using PSPHOT.SOURCES filerule)
+    // select subset of stars for astrometry
+    if (!psastroDataLoad (config)) {
+        psErrorStackPrint(stderr, "error loading input data\n");
+        exit (1);
+    }
+
+    // run the full astrometry analysis (chip and/or mosaic)
+    if (!psastroAnalysis (config)) {
+        psErrorStackPrint(stderr, "failure in psastro analysis\n");
+        exit (1);
+    }
+
+    // write out the results
+    if (!psastroDataSave (config)) {
+        psErrorStackPrint(stderr, "failed to write out data\n");
+        exit (1);
+    }
+
+    psLogMsg ("psastro", 3, "complete psastro run: %f sec\n", psTimerMark ("complete"));
+
+    psastroCleanup (config);
+    exit (EXIT_SUCCESS);
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastro.h
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastro.h	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastro.h	(revision 23321)
@@ -0,0 +1,136 @@
+/** @file psastro.h
+ *
+ *  @brief This file defines the library functions available to external
+ *  programs.
+ *
+ *  It must be included by programs which are compiled against
+ *  psphot functions.
+ *
+ *  @ingroup psastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.49 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-09 21:25:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# ifndef PSASTRO_H
+# define PSASTRO_H
+
+# include "psastroErrorCodes.h"
+
+/// @addtogroup psastro
+/// @{
+
+# define PSASTRO_RECIPE "PSASTRO" ///< Name of the recipe to use
+
+# define psMemCopy(A)(psMemIncrRefCounter((A)))
+# define DEG_RAD 57.295779513082322
+# define RAD_DEG  0.017453292519943
+# define SIGN(X)  (((X) == 0) ? 0 : ((fabs((double)(X))) / (X)))
+
+#if 0
+/**
+ * this structure represents a fit to the logN / logS curve for a set of stars
+ * logN = offset + slope * logS
+ */
+typedef struct {
+    double mMin;                        ///< minimum magnitude bin with data
+    double mMax;                        ///< maximum magnitude bin with data
+    double offset;                      ///< fitted line offset
+    double slope;                       ///< fitted line slope
+    double mPeak;                       ///< mag of peak bin
+    int nPeak;                          ///< # of stars in peak bin
+    int sPeak;                          ///< sum of stars to peak bin
+} pmLumFunc;
+#endif
+
+bool              psastroDataSave (pmConfig *config);
+bool              psastroDefineFiles (pmConfig *config, pmFPAfile *input);
+bool              psastroDefineFile (pmConfig *config, pmFPA *input, char *filerule, char *argname, pmFPAfileType fileType, pmDetrendType detrendType);
+bool              psastroAnalysis (pmConfig *config);
+
+bool              psastroConvertFPA (pmFPA *fpa, psMetadata *recipe);
+bool              psastroConvertReadout (pmReadout *readout, psMetadata *recipe);
+psArray          *pmSourceToAstromObj (psArray *sources);
+bool              psastroAstromGuess (int *nStars, pmConfig *config);
+bool              psastroAstromGuessCheck (pmConfig *config);
+bool              psastroMaskUpdates (pmConfig *config);
+
+psPlaneDistort   *psPlaneDistortIdentity ();
+bool              psPlaneDistortIsIdentity (psPlaneDistort *distort);
+
+psArray          *psastroLoadRefstars (pmConfig *config);
+bool              psastroChipAstrom (pmConfig *config);
+bool              psastroOneChip (pmFPA *fpa, pmChip *chip, psArray *refset, psArray *rawset, psMetadata *recipe, psMetadata *updates);
+bool              psastroOneChipGrid (pmFPA *fpa, pmChip *chip, psArray *refset, psArray *rawset, psMetadata *recipe, psMetadata *updates);
+bool              psastroOneChipFit (pmFPA *fpa, pmChip *chip, psArray *refset, psArray *rawset, psMetadata *recipe, psMetadata *updates);
+bool              psastroChooseRefstars (pmConfig *config, psArray *refs);
+bool              psastroRefstarSubset (pmReadout *readout);
+pmLumFunc        *psastroLuminosityFunction (psArray *stars, pmLumFunc *rawFunc);
+bool              psastroLuminosityFunctionPlot(psVector *lnMag, psVector *Mag, pmLumFunc *lumFunc, pmLumFunc *rawFunc);
+psArray          *psastroRemoveClumps (psArray *input, int scale);
+
+
+// utility functions:
+bool              psastroUpdateChipToFPA (pmFPA *fpa, pmChip *chip, psArray *rawstars, psArray *refstars);
+bool              psastroWriteStars (char *filename, psArray *sources);
+bool              psastroWriteTransform (psPlaneTransform *map);
+
+// mosaic fitting functions
+bool              psastroMosaicDistortion (pmFPA *fpa, psMetadata *recipe, int pass);
+psPlaneTransform *psastroMosaicFitRotAndScale (pmFPA *fpa);
+bool              psastroMosaicApplyRotAndScale (pmFPA *fpa, psPlaneTransform *TPtoFP);
+bool              psastroMosaicDistortionFromGradients (pmFPA *fpa, psMetadata *recipe);
+bool              psastroMosaicCorrectDistortion (pmFPA *fpa, psPlaneTransform *TPtoFP);
+bool              psastroMosaicCommonScale (pmFPA *fpa, psMetadata *recipe);
+bool              psastroMosaicAstrom (pmConfig *config);
+bool              psastroMosaicChipAstrom (pmFPA *fpa, psMetadata *recipe, int iteration);
+bool              psastroMosaicSetMatch (pmFPA *fpa, psMetadata *recipe, int iteration);
+bool              psastroMosaicSetAstrom (pmFPA *fpa);
+bool              psastroMosaicHeaders (pmConfig *config);
+bool              psastroMosaicRescaleChips (pmFPA *fpa);
+bool              psastroMosaicOneChip (pmChip *chip, pmReadout *readout, psMetadata *recipe, psMetadata *updates, int iteration);
+
+// Return version strings.
+psString          psastroVersion(void);
+psString          psastroSource(void);
+psString          psastroVersionLong(void);
+bool              psastroVersionHeader(psMetadata *header);
+bool              psastroVersionHeaderFull(psMetadata *header);
+void              psastroVersionPrint(void);
+
+// demo plots
+bool              psastroPlotRawstars (psArray *rawstars, pmFPA *fpa, pmChip *chip, psMetadata *recipe);
+bool              psastroPlotRefstars (psArray *refstars, psMetadata *recipe);
+bool              psastroPlotOneChipFit (psArray *rawstars, psArray *refstars, psArray *match, psMetadata *recipe);
+
+bool              psastroDumpRawstars (psArray *rawstars, pmFPA *fpa, pmChip *chip);
+bool              psastroDumpRefstars (psArray *refstars, char *filename);
+bool              psastroDumpMatches (pmFPA *fpa, char *filename);
+bool              psastroDumpStars (psArray *stars, char *filename);
+bool              psastroDumpMatchedStars (char *filename, psArray *rawstars, psArray *refstars, psArray *match);
+bool              psastroDumpGradients (psArray *gradients, char *filename);
+
+bool              psastroMosaicSetAstrom_tmp (pmFPA *fpa);
+int               psastroSortByMag (const void *a, const void *b);
+
+bool              psastroFixChips (pmConfig *config, psMetadata *recipe);
+bool              psastroFixChipsTest (pmConfig *config, psMetadata *recipe);
+bool              psastroUseModel (pmConfig *config, psMetadata *recipe);
+bool              psastroDumpCorners (char *filenameU, char *filenameD, pmFPA *fpa);
+
+
+psArray          *psastroReadGetstarCatalog (psFits *fits);
+psArray          *psastroReadGetstar_PS1_DEV_0 (psFits *fits);
+
+bool              psastroAstromGuessSetChip (pmFPA *fpa, pmChip *chip, const pmFPAview *view, double pixelScale, bool bilevelAstrometry);
+bool              psastroAstromGuessSetFPA (pmFPA *fpa, bool *bilevelAstrometry);
+bool              psastroMetadataStats (pmConfig *config);
+
+bool psastroZeroPoint (pmConfig *config);
+bool psastroZeroPointReadout(pmReadout *readout, float zeropt, float exptime);
+bool psastroZeroPointFromRecipe (float *zeropt, float *exptime, pmFPA *fpa, psMetadata *recipe);
+
+///@}
+# endif /* PSASTRO_H */
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroAnalysis.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroAnalysis.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroAnalysis.c	(revision 23321)
@@ -0,0 +1,104 @@
+/** @file psastroAnalysis.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.15 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+bool psastroAnalysis (pmConfig *config) {
+
+    bool status;
+    int nStars;
+
+    // measure the total elapsed time in psastroAnalysis.
+    psTimerStart ("psastroAnalysis");
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe");
+        return false;
+    }
+
+    if (!psastroUseModel (config, recipe)) {
+        psError (PSASTRO_ERR_UNKNOWN, false, "failed to set model astrometry\n");
+        return false;
+    }
+
+    // interpret the available initial astrometric information
+    // apply the initial guess
+    if (!psastroAstromGuess (&nStars, config)) {
+        psError (PSASTRO_ERR_UNKNOWN, false, "failed to determine initial astrometry guess\n");
+        return false;
+    }
+    if (nStars == 0) {
+        psLogMsg ("psastro", 2, "skipping astrometry analysis : no stars\n");
+        return false;
+    }
+
+    // load the reference stars overlapping the data stars
+    psArray *refs = psastroLoadRefstars(config);
+    if (!refs) {
+        psError (PSASTRO_ERR_UNKNOWN, false, "failed to load reference data\n");
+        return false;
+    }
+    if (refs->n == 0) {
+        psError(PSASTRO_ERR_REFSTARS, true, "no reference stars found");
+        psFree(refs);
+        return false;
+    }
+
+    if (!psastroChooseRefstars (config, refs)) {
+        psError (PSASTRO_ERR_UNKNOWN, false, "failed to select reference data for chips\n");
+        psFree(refs);
+        return false;
+    }
+
+    // check the command-line arguments first
+    bool chipastro = psMetadataLookupBool (&status, config->arguments, "PSASTRO.CHIP.MODE");
+    if (!status) {
+        chipastro = psMetadataLookupBool (&status, recipe, "PSASTRO.CHIP.MODE");
+    }
+    bool mosastro  = psMetadataLookupBool (&status, config->arguments, "PSASTRO.MOSAIC.MODE");
+    if (!status) {
+        mosastro  = psMetadataLookupBool (&status, recipe, "PSASTRO.MOSAIC.MODE");
+    }
+    if (!chipastro && !mosastro) {
+        psLogMsg ("psastro", 3, "no astrometry mode selected, assuming chip astrometry\n");
+        chipastro = true;
+    }
+
+    if (chipastro) {
+        if (!psastroChipAstrom (config)) {
+            psError (PSASTRO_ERR_UNKNOWN, false, "failed to perform single chip astrometry\n");
+            psFree(refs);
+            return false;
+        }
+    }
+    if (mosastro) {
+        if (!psastroMosaicAstrom (config)) {
+            psError (PSASTRO_ERR_UNKNOWN, false, "failed to perform mosaic camera astrometry\n");
+            psFree(refs);
+            return false;
+        }
+    }
+
+    psastroZeroPoint (config);
+
+    psastroAstromGuessCheck (config);
+
+    psastroMaskUpdates (config);
+
+    // XXX how do we specify stack astrometry?
+    // psastroStackAstrom (config, refs);
+
+    psFree (refs);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroArguments.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroArguments.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroArguments.c	(revision 23321)
@@ -0,0 +1,130 @@
+/** @file psastroArguments.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.34.2.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-19 17:59:50 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroStandAlone.h"
+
+pmConfig *psastroArguments (int argc, char **argv) {
+
+    bool status;
+    int N;
+
+    if (argc == 1) {
+        psError(PSASTRO_ERR_ARGUMENTS, true, "No arguments supplied");
+        psErrorStackPrint(stderr, "exit");
+        return NULL;
+    }
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSASTRO_RECIPE);
+    if (config == NULL) {
+        psError(PSASTRO_ERR_CONFIG, false, "Can't read site configuration");
+        psErrorStackPrint(stderr, "exit");
+        return NULL;
+    }
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PSASTRO recipe values loaded from recipe files
+    psMetadata *options = pmConfigRecipeOptions (config, PSASTRO_RECIPE);
+
+    // photcode : used in output to supplement header data (argument or recipe?)
+    if ((N = psArgumentGet (argc, argv, "-photcode"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (options, PS_LIST_TAIL, "PHOTCODE", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // 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_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    // apply the chip correction based on the reference astrometry?
+    if ((N = psArgumentGet (argc, argv, "-fixchips"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddBool (config->arguments, PS_LIST_TAIL, "PSASTRO.FIX.CHIPS", PS_META_REPLACE, "", true);
+    }
+    // no valid header WCS: supply from astrom model
+    if ((N = psArgumentGet (argc, argv, "-use-astrommodel"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddBool (config->arguments, PS_LIST_TAIL, "PSASTRO.USE.MODEL", PS_META_REPLACE, "", true);
+    }
+    // define the reference astrometry file
+    status = pmConfigFileSetsMD (config->arguments, &argc, argv, "ASTROM.MODEL", "-astrommodel", "-astrommodellist");
+    if (status) {
+        // if supplied, assume -fixchips is desired
+        psMetadataAddBool (config->arguments, PS_LIST_TAIL, "PSASTRO.FIX.CHIPS", PS_META_REPLACE, "", true);
+    }
+
+    // define the reference astrometry file
+    status = pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT.MASK", "-mask", "-masklist");
+    status = pmConfigFileSetsMD (config->arguments, &argc, argv, "REFMASK", "-refmask", "-refmasklist");
+
+    if ((N = psArgumentGet(argc, argv, "-stats"))) {
+        psArgumentRemove(N, &argc, argv);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "STATS", PS_META_REPLACE, "Filename for summary statistics", argv[N]);
+        psArgumentRemove(N, &argc, argv);
+    }
+
+    // apply mosastro mode?
+    if ((N = psArgumentGet (argc, argv, "-mosastro"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddBool (config->arguments, PS_LIST_TAIL, "PSASTRO.MOSAIC.MODE", PS_META_REPLACE, "", false);
+    }
+    if ((N = psArgumentGet (argc, argv, "+mosastro"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddBool (config->arguments, PS_LIST_TAIL, "PSASTRO.MOSAIC.MODE", PS_META_REPLACE, "", true);
+    }
+
+    // apply chipastro mode?
+    if ((N = psArgumentGet (argc, argv, "-chipastro"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddBool (config->arguments, PS_LIST_TAIL, "PSASTRO.CHIP.MODE", PS_META_REPLACE, "", false);
+    }
+    if ((N = psArgumentGet (argc, argv, "+chipastro"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddBool (config->arguments, PS_LIST_TAIL, "PSASTRO.CHIP.MODE", PS_META_REPLACE, "", true);
+    }
+
+    // run in visual mode?
+    if ((N = psArgumentGet (argc, argv, "-visual"))) {
+        psArgumentRemove (N, &argc, argv);
+        pmVisualSetVisual (true);
+    }
+
+    // dump the configuration to a file?
+    if ((N = psArgumentGet (argc, argv, "-dumpconfig"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddStr (config->arguments, PS_LIST_TAIL, "DUMP_CONFIG", PS_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+
+    status = pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT", "-file", "-list");
+    if (!status) {
+        psError(PSASTRO_ERR_ARGUMENTS, true, "Missing -file (input) or -list (input)");
+        psErrorStackPrint(stderr, "exit");
+        return NULL;
+    }
+
+    if (argc != 2) {
+        psError(PSASTRO_ERR_ARGUMENTS, true, "Incorrect arguments supplied");
+        psErrorStackPrint(stderr, "exit");
+        return NULL;
+    }
+
+    // output positions is fixed
+    psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[1]);
+
+    psTrace("psastro", 1, "Done with psastroArguments...\n");
+    return (config);
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroAstromGuess.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroAstromGuess.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroAstromGuess.c	(revision 23321)
@@ -0,0 +1,442 @@
+/** @file psastroAstromGuess.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.35 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-09 21:25:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+# define DEBUG 0
+
+/**
+ * \brief This function loads the header WCS astrometry terms into the fpa 
+ * terms and applies the astrometry to the detected objects.
+ * 
+ * This function assumes the initial astrometry arrives in the form of WCS 
+ * keywords in the headers corresponding to the chips.
+ */
+bool psastroAstromGuess (int *nStars, pmConfig *config) {
+
+    bool newFPA = true;
+    bool status = false;
+    double RAmin  = +FLT_MAX;
+    double RAmax  = -FLT_MAX;
+    double DECmin = +FLT_MAX;
+    double DECmax = -FLT_MAX;
+
+    double RAminSky = NAN;
+    double RAmaxSky = NAN;
+
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+
+    *nStars = 0;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe!");
+        return false;
+    }
+
+    // have we already supplied the astrometry from the model?
+    bool useModel = psMetadataLookupBool (&status, config->arguments, "PSASTRO.USE.MODEL");
+    if (!status) {
+        useModel = psMetadataLookupBool (&status, recipe, "PSASTRO.USE.MODEL");
+    }
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find input data");
+        return false;
+    }
+
+    // physical pixel scale in microns per pixel
+    double pixelScale = psMetadataLookupF32 (&status, recipe, "PSASTRO.PIXEL.SCALE");
+    if (!status) {
+        psError(PS_ERR_IO, true, "Failed to lookup pixel scale");
+        return false;
+    }
+
+    psVector *cornerL = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *cornerM = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *cornerP = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *cornerQ = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *cornerR = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *cornerD = psVectorAllocEmpty (100, PS_TYPE_F32);
+
+    pmFPA *fpa = input->fpa;
+
+    if (DEBUG) psastroDumpCorners ("corners.up.guess1.dat", "corners.dn.guess1.dat", fpa);
+
+    // load mosaic-level astrometry?
+    bool bilevelAstrometry = false;
+    if (!useModel) {
+        psastroAstromGuessSetFPA (fpa, &bilevelAstrometry);
+    }
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists || !chip->data_exists) { continue; }
+
+        if (!useModel) {
+            if (!psastroAstromGuessSetChip (fpa, chip, view, pixelScale, bilevelAstrometry)) continue;
+        }
+
+        if (newFPA) {
+            newFPA = false;
+            while (fpa->toSky->R <        0) fpa->toSky->R += 2.0*M_PI;
+            while (fpa->toSky->R > 2.0*M_PI) fpa->toSky->R -= 2.0*M_PI;
+            RAminSky = fpa->toSky->R - M_PI;
+            RAmaxSky = fpa->toSky->R + M_PI;
+        }
+
+        // report and save the current best guess for the chip 0,0 pixel coordinates
+        {
+            psPlane ptCH, ptFP, ptTP;
+            psSphere ptSky;
+
+            ptCH.x = 0;
+            ptCH.y = 0;
+            psPlaneTransformApply (&ptFP, chip->toFPA, &ptCH);
+            psPlaneTransformApply (&ptTP, fpa->toTPA, &ptFP);
+            psDeproject (&ptSky, &ptTP, fpa->toSky);
+            psLogMsg ("psastro", 3, "0,0 pix for chip %3d = %f,%f\n", view->chip, DEG_RAD*ptSky.r, DEG_RAD*ptSky.d);
+
+            psVectorAppend (cornerL, ptFP.x);
+            psVectorAppend (cornerM, ptFP.y);
+            psVectorAppend (cornerP, ptTP.x);
+            psVectorAppend (cornerQ, ptTP.y);
+            psVectorAppend (cornerR, ptSky.r);
+            psVectorAppend (cornerD, ptSky.d);
+        }
+
+        // apply the new WCS guess data to all of the data in the readouts
+        while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+            // process each of the readouts
+            while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+                if (! readout->data_exists) { continue; }
+
+                psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+                if (rawstars == NULL) { continue; }
+
+                *nStars += rawstars->n;
+                for (int i = 0; i < rawstars->n; i++) {
+                    pmAstromObj *raw = rawstars->data[i];
+
+                    psPlaneTransformApply (raw->FP, chip->toFPA, raw->chip);
+                    psPlaneTransformApply (raw->TP, fpa->toTPA, raw->FP);
+                    psDeproject (raw->sky, raw->TP, fpa->toSky);
+
+                    // rationalize ra to sky range centered on boresite
+                    while (raw->sky->r < RAminSky) raw->sky->r += 2.0*M_PI;
+                    while (raw->sky->r > RAmaxSky) raw->sky->r -= 2.0*M_PI;
+
+                    RAmin = PS_MIN (raw->sky->r, RAmin);
+                    RAmax = PS_MAX (raw->sky->r, RAmax);
+
+                    DECmin = PS_MIN (raw->sky->d, DECmin);
+                    DECmax = PS_MAX (raw->sky->d, DECmax);
+                }
+
+                // dump or plot the resulting projected positions
+                if (psTraceGetLevel("psastro.dump") > 0) {
+                    psastroDumpRawstars (rawstars, fpa, chip);
+                }
+
+                pmAstromVisualPlotRawStars(rawstars, fpa, chip, recipe);
+
+                if (psTraceGetLevel("psastro.plot") > 0) {
+                    psastroPlotRawstars (rawstars, fpa, chip, recipe);
+                }
+            }
+        }
+    }
+
+    if (DEBUG) psastroDumpCorners ("corners.up.guess2.dat", "corners.dn.guess2.dat", fpa);
+
+    // how many total sources are available to us?
+    psMetadataAddS32 (recipe, PS_LIST_TAIL, "NTOTSTAR",  PS_META_REPLACE, "", *nStars);
+    if (*nStars == 0) {
+        psLogMsg ("psastro", 2, "no sources available for astrometry\n");
+        psFree (view);
+        return true;
+    }
+
+    psLogMsg ("psastro", 2, "loaded raw data from %f,%f to %f,%f\n",
+              DEG_RAD*RAmin, DEG_RAD*DECmin,
+              DEG_RAD*RAmax, DEG_RAD*DECmax);
+
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "RA_MIN",  PS_META_REPLACE, "", RAmin);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "RA_MAX",  PS_META_REPLACE, "", RAmax);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "DEC_MIN", PS_META_REPLACE, "", DECmin);
+    psMetadataAddF32 (recipe, PS_LIST_TAIL, "DEC_MAX", PS_META_REPLACE, "", DECmax);
+
+    psMetadataAddVector (input->fpa->analysis, PS_LIST_TAIL, "CORNER.L", PS_META_REPLACE, "corner pixel", cornerL);
+    psMetadataAddVector (input->fpa->analysis, PS_LIST_TAIL, "CORNER.M", PS_META_REPLACE, "corner pixel", cornerM);
+    psMetadataAddVector (input->fpa->analysis, PS_LIST_TAIL, "CORNER.P", PS_META_REPLACE, "corner pixel", cornerP);
+    psMetadataAddVector (input->fpa->analysis, PS_LIST_TAIL, "CORNER.Q", PS_META_REPLACE, "corner pixel", cornerQ);
+    psMetadataAddVector (input->fpa->analysis, PS_LIST_TAIL, "CORNER.R", PS_META_REPLACE, "corner pixel", cornerR);
+    psMetadataAddVector (input->fpa->analysis, PS_LIST_TAIL, "CORNER.D", PS_META_REPLACE, "corner pixel", cornerD);
+
+    psFree (cornerL);
+    psFree (cornerM);
+    psFree (cornerP);
+    psFree (cornerQ);
+    psFree (cornerR);
+    psFree (cornerD);
+
+    psFree (view);
+    return true;
+}
+
+/* coordinate frame hierachy
+   pixels (on a given readout)
+   cell
+   chip
+   FP (focal plane)
+   TP (tangent plane)
+   sky (ra, dec)
+*/
+
+bool psastroAstromGuessSetChip (pmFPA *fpa, pmChip *chip, const pmFPAview *view, double pixelScale, bool bilevelAstrometry) {
+
+    // read WCS data from the corresponding header
+    pmHDU *hdu = pmFPAviewThisHDU (view, fpa);
+    if (bilevelAstrometry) {
+        if (!pmAstromReadBilevelChip (chip, hdu->header)) {
+            psWarning("Could not get WCS information from header for chip %d, skipping", view->chip);
+            return false;
+        }
+    } else {
+        if (!pmAstromReadWCS (fpa, chip, hdu->header, pixelScale)) {
+            psWarning("Could not get WCS information from header for chip %d, skipping", view->chip);
+            return false;
+        }
+    }
+    return true;
+}
+
+bool psastroAstromGuessSetFPA (pmFPA *fpa, bool *bilevelAstrometry) {
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmHDU *phu = pmFPAviewThisPHU (view, fpa);
+
+    *bilevelAstrometry = false;
+
+    // load mosaic-level astrometry?
+    if (phu) {
+        char *ctype = psMetadataLookupStr (NULL, phu->header, "CTYPE1");
+        if (ctype) {
+            *bilevelAstrometry = !strcmp (&ctype[4], "-DIS");
+        }
+    }
+    if (*bilevelAstrometry) {
+        pmAstromReadBilevelMosaic (fpa, phu->header);
+    }
+    psFree (view);
+    return true;
+}
+
+// we made a guess at the beginning; how does the guess compare with the result?
+bool psastroAstromGuessCheck (pmConfig *config) {
+
+    bool status;
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find input data");
+        return false;
+    }
+
+    pmFPA *fpa = input->fpa;
+
+    psVector *cornerLo = psMetadataLookupPtr (&status, input->fpa->analysis, "CORNER.L");
+    psVector *cornerMo = psMetadataLookupPtr (&status, input->fpa->analysis, "CORNER.M");
+    psVector *cornerPo = psMetadataLookupPtr (&status, input->fpa->analysis, "CORNER.P");
+    psVector *cornerQo = psMetadataLookupPtr (&status, input->fpa->analysis, "CORNER.Q");
+    psVector *cornerRo = psMetadataLookupPtr (&status, input->fpa->analysis, "CORNER.R");
+    psVector *cornerDo = psMetadataLookupPtr (&status, input->fpa->analysis, "CORNER.D");
+
+    if (cornerLo->n < 3) return true;
+
+    psVector *cornerLn = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *cornerMn = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *cornerPn = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *cornerQn = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *cornerRn = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *cornerDn = psVectorAllocEmpty (100, PS_TYPE_F32);
+
+    psVector *cornerMK = psVectorAllocEmpty (100, PS_TYPE_VECTOR_MASK);
+
+    if (DEBUG) psastroDumpCorners ("corners.up.guess3.dat", "corners.dn.guess3.dat", fpa);
+
+    pmChip *chip = NULL;
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        if (!chip->process || !chip->file_exists || !chip->data_exists) { continue; }
+
+        // XXX we are currently inconsistent with marking the good vs the bad data
+        // psastroChipAstrom sets data_exists to false if the fit is bad.  this is
+        // probably wrong since it implies there is no data!
+
+        // skip chips for which the astrometry failed (NASTRO == 0)
+        if (!chip->cells->n) goto skip_chip;
+        pmCell *cell = chip->cells->data[0];
+        if (!cell) goto skip_chip;
+
+        if (!cell->readouts->n) goto skip_chip;
+        pmReadout *readout = cell->readouts->data[0];
+        if (!readout) goto skip_chip;
+
+        psMetadata *updates = psMetadataLookupMetadata (&status, readout->analysis, "PSASTRO.HEADER");
+        if (!updates) goto skip_chip;
+
+        int nAstro = psMetadataLookupS32 (&status, updates, "NASTRO");
+        if (!nAstro) goto skip_chip;
+
+        float astError = psMetadataLookupF32 (&status, updates, "CERROR");
+        if (fabs(astError) < 1e-6) goto skip_chip;
+
+        psPlane ptCH, ptFP, ptTP;
+        psSphere ptSky;
+
+        ptCH.x = 0;
+        ptCH.y = 0;
+        psPlaneTransformApply (&ptFP, chip->toFPA, &ptCH);
+        psPlaneTransformApply (&ptTP, fpa->toTPA, &ptFP);
+        psDeproject (&ptSky, &ptTP, fpa->toSky);
+        psLogMsg ("psastro", 3, "0,0 pix for chip %3d = %f,%f\n", view->chip, DEG_RAD*ptSky.r, DEG_RAD*ptSky.d);
+
+        // new corner locations based on the calibrated astrometry
+        psVectorAppend (cornerLn, ptFP.x);
+        psVectorAppend (cornerMn, ptFP.y);
+        psVectorAppend (cornerPn, ptTP.x);
+        psVectorAppend (cornerQn, ptTP.y);
+        psVectorAppend (cornerRn, ptSky.r);
+        psVectorAppend (cornerDn, ptSky.d);
+        psVectorAppend (cornerMK, 0);
+        continue;
+
+    skip_chip:
+        // new corner locations based on the calibrated astrometry
+        psVectorAppend (cornerLn, 0.0);
+        psVectorAppend (cornerMn, 0.0);
+        psVectorAppend (cornerPn, 0.0);
+        psVectorAppend (cornerQn, 0.0);
+        psVectorAppend (cornerRn, 0.0);
+        psVectorAppend (cornerDn, 0.0);
+        psVectorAppend (cornerMK, 1);
+    }
+
+    // compare the old R,D values projected to the same tangent plane as the new R,D values:
+
+    psVector *cornerPs = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *cornerQs = psVectorAllocEmpty (100, PS_TYPE_F32);
+
+    for (int i = 0; i < cornerRo->n; i++) {
+
+        psPlane ptTP;
+        psSphere ptSky;
+
+        ptSky.r = cornerRo->data.F32[i];
+        ptSky.d = cornerDo->data.F32[i];
+
+        psProject (&ptTP, &ptSky, fpa->toSky);
+        psVectorAppend (cornerPs, ptTP.x);
+        psVectorAppend (cornerQs, ptTP.y);
+    }
+
+    psPlaneTransform *map = psPlaneTransformAlloc (1, 1);
+    map->x->coeffMask[1][1] = PS_POLY_MASK_SET;
+    map->y->coeffMask[1][1] = PS_POLY_MASK_SET;
+
+    // fit the valid chips, mask the invalid chips
+    psVectorFitPolynomial2D (map->x, cornerMK, 1, cornerPn, NULL, cornerPs, cornerQs);
+    psVectorFitPolynomial2D (map->y, cornerMK, 1, cornerQn, NULL, cornerPs, cornerQs);
+
+    // apply the linear fit...
+    psVector *cornerPf = psPolynomial2DEvalVector (map->x, cornerPs, cornerQs);
+    psVector *cornerQf = psPolynomial2DEvalVector (map->y, cornerPs, cornerQs);
+
+    // ...and calculate the residual between Pn,Qn and Pf,Qf
+    psVector *cornerPd = (psVector *) psBinaryOp (NULL, cornerPn, "-", cornerPf);
+    psVector *cornerQd = (psVector *) psBinaryOp (NULL, cornerQn, "-", cornerQf);
+
+    pmAstromVisualPlotAstromGuessCheck (cornerPo, cornerQo, cornerPn, cornerQn, cornerPd, cornerQd);
+
+    psStats *statsP = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+    psStats *statsQ = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_SAMPLE_STDEV);
+
+    psVectorStats (statsP, cornerPd, NULL, cornerMK, 1);
+    psVectorStats (statsQ, cornerQd, NULL, cornerMK, 1);
+
+    float angle = atan2 (map->y->coeff[1][0], map->x->coeff[1][0]);
+    float scale = hypot (map->y->coeff[1][0], map->x->coeff[1][0]);
+
+    psLogMsg ("psastro", 3, "boresite offset  : %f,%f\n", map->x->coeff[0][0], map->y->coeff[0][0]);
+    psLogMsg ("psastro", 3, "boresite angle   : %f, scale: %f", angle*PS_DEG_RAD, scale);
+    psLogMsg ("psastro", 3, "boresite scatter : %f,%f\n", statsP->sampleStdev, statsQ->sampleStdev);
+
+    // write the elapsed time here; this will be updated in psastroMosaicAstrometry, if called
+    psMetadata *header = psMetadataLookupMetadata (&status, input->fpa->analysis, "PSASTRO.HEADER");
+    if (!header) {
+        header = psMetadataAlloc();
+        psMetadataAddMetadata (input->fpa->analysis, PS_LIST_TAIL, "PSASTRO.HEADER",  PS_META_REPLACE, "psastro header stats", header);
+        psFree (header);  // drop this reference
+    }
+
+    psMetadataAddF32 (header, PS_LIST_TAIL, "AST_R0", PS_META_REPLACE, "boresite offset in RA (TP units)", map->x->coeff[0][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "AST_D0", PS_META_REPLACE, "boresite offset in DEC (TP units)", map->y->coeff[0][0]);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "AST_T0", PS_META_REPLACE, "boresite angle (degrees)", angle*PS_DEG_RAD);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "AST_S0", PS_META_REPLACE, "boresite scale correction", scale);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "AST_RS", PS_META_REPLACE, "boresite scatter in RA (TP units)", statsP->sampleStdev);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "AST_DS", PS_META_REPLACE, "boresite scatter in DEC (TP units)", statsQ->sampleStdev);
+
+    if (DEBUG) {
+        FILE *f = fopen ("corners.dat", "w");
+        for (int i = 0; i < cornerRo->n; i++) {
+            fprintf (f, "%10.6f %10.6f  %9.2f %9.2f  %9.2f %9.2f  |  %10.6f %10.6f  %9.2f %9.2f  %9.2f %9.2f\n",
+                     cornerRn->data.F32[i], cornerDn->data.F32[i], cornerPn->data.F32[i], cornerQn->data.F32[i], cornerLn->data.F32[i], cornerMn->data.F32[i],
+                     cornerRo->data.F32[i], cornerDo->data.F32[i], cornerPo->data.F32[i], cornerQo->data.F32[i], cornerLo->data.F32[i], cornerMo->data.F32[i]);
+        }
+        fclose (f);
+    }
+
+    psFree (cornerPf);
+    psFree (cornerQf);
+    psFree (cornerPd);
+    psFree (cornerQd);
+
+    psFree (statsP);
+    psFree (statsQ);
+
+    psFree (cornerMK);
+
+    psFree (cornerLn);
+    psFree (cornerMn);
+    psFree (cornerPn);
+    psFree (cornerQn);
+    psFree (cornerRn);
+    psFree (cornerDn);
+    psFree (cornerPs);
+    psFree (cornerQs);
+    psFree (map);
+    psFree (view);
+
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroChipAstrom.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroChipAstrom.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroChipAstrom.c	(revision 23321)
@@ -0,0 +1,148 @@
+/** @file psastroChipAstrom.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.29 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+# define NONLIN_TOL 0.001 ///< tolerance in pixels
+
+bool psastroChipAstrom (pmConfig *config) {
+
+    bool status;
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe");
+        return false;
+    }
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (&status, config->files, "PSASTRO.INPUT");
+    if (!input) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find input data");
+        return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmFPA *fpa = input->fpa;
+
+    int numGoodChips = 0;               // Number of chips for which astrometry succeeds
+
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+        int numGoodCells = 0;           // Number of cells for which astrometry succeeds
+        while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+            if (!chip->fromFPA) { continue; }
+
+            // process each of the readouts
+            int numGoodRO = 0;          // Number of readouts for which astrometry succeeds
+            while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+                if (! readout->data_exists) { continue; }
+
+                // select the raw objects for this readout
+                psArray *rawstars = psMetadataLookupPtr (&status, readout->analysis, "PSASTRO.RAWSTARS");
+                if (rawstars == NULL) { continue; }
+
+                // select the raw objects for this readout
+                psArray *refstars = psMetadataLookupPtr (&status, readout->analysis, "PSASTRO.REFSTARS");
+                if (refstars == NULL) { continue; }
+
+                // the absolute minimum number of stars is 4 (for order = 1)
+                if ((rawstars->n < 4) || (refstars->n < 4)) {
+                    readout->data_exists = false;
+                    psLogMsg ("psastro", 3, "insufficient rawstars (%ld) or refstars (%ld)",
+                              rawstars->n, refstars->n);
+                    continue;
+                }
+
+                // save WCS and analysis metadata in update header
+		// (pull or create local view to entry on readout->analysis)
+		psMetadata *updates = psMetadataLookupMetadata (&status, readout->analysis, "PSASTRO.HEADER");
+		if (!updates) {
+		    updates = psMetadataAlloc ();
+		    psMetadataAddMetadata (readout->analysis, PS_LIST_TAIL, "PSASTRO.HEADER",  PS_META_REPLACE, "psastro header stats", updates);
+		    psFree (updates);
+		}
+
+                // XXX update the header with info to reflect the failure
+                if (!psastroOneChipGrid (fpa, chip, refstars, rawstars, recipe, updates)) {
+                    readout->data_exists = false;
+                    psLogMsg ("psastro", 3, "failed to find a solution\n");
+                    continue;
+                }
+                // XXX update the header with info to reflect the failure
+                if (!psastroOneChipFit (fpa, chip, refstars, rawstars, recipe, updates)) {
+                    readout->data_exists = false;
+                    psLogMsg ("psastro", 3, "failed to find a solution\n");
+                    continue;
+                }
+
+                numGoodRO++;
+
+                // write the elapsed time here; this will be updated in psastroMosaicAstrometry, if called
+                psMetadataAddF32 (updates, PS_LIST_TAIL, "DT_ASTR", PS_META_REPLACE, "elapsed psastro time", psTimerMark ("psastroAnalysis"));
+
+                pmAstromWriteWCS (updates, fpa, chip, NONLIN_TOL);
+
+                if (psTraceGetLevel("psastro.dump") > 0) {
+
+                    char *filename = NULL;
+                    char *chipname = psMetadataLookupStr (&status, chip->concepts, "CHIP.NAME");
+
+                    psStringAppend (&filename, "rawstars.ch.%s.dat", chipname);
+                    psastroDumpStars (rawstars, filename);
+                    psFree (filename);
+                    filename = NULL;
+
+                    psStringAppend (&filename, "refstars.ch.%s.dat", chipname);
+                    psastroDumpStars (refstars, filename);
+                    psFree (filename);
+                    filename = NULL;
+                }
+            }
+            if (numGoodRO > 0) {
+                numGoodCells++;
+            }
+        }
+        if (numGoodCells > 0) {
+            numGoodChips++;
+        }
+    }
+
+    if (fpa->chips->n == 1 && numGoodChips == 0) {
+        psError(PSASTRO_ERR_UNKNOWN, false, "Failed to fit single chip.");
+        return false;
+    }
+
+    if (!psastroFixChips (config, recipe)) {
+        psError(PSASTRO_ERR_UNKNOWN, false, "failed to align problematic chips");
+        return false;
+    }
+
+    psFree (view);
+    return true;
+}
+
+/* coordinate frame hierachy
+   pixels (on a given readout)
+   cell
+   chip
+   FP (focal plane)
+   TP (tangent plane)
+   sky (ra, dec)
+*/
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroChooseRefstars.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroChooseRefstars.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroChooseRefstars.c	(revision 23321)
@@ -0,0 +1,145 @@
+/** @file psastroChooseRefstars.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.21 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+# define ESCAPE { \
+  psError(PS_ERR_UNKNOWN, false, "Failure in psastroChooseRefstars"); \
+  psFree (index); \
+  psFree (view); \
+  return false; \
+}
+
+bool psastroChooseRefstars (pmConfig *config, psArray *refs) {
+
+    bool status;
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe!\n");
+        return false;
+    }
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find input data!\n");
+        return false;
+    }
+
+    // extra field fraction to add
+    double fieldPadding = psMetadataLookupF32 (&status, recipe, "PSASTRO.FIELD.PADDING");
+    if (!status) fieldPadding = 0.0;
+
+    bool matchLumFunc = psMetadataLookupBool (&status, recipe, "PSASTRO.MATCH.LUMFUNC");
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmFPA *fpa = input->fpa;
+
+    // sort by mag
+    psVector *index = psArraySortIndex (NULL, refs, psastroSortByMag);
+
+    int nMax = psMetadataLookupS32 (&status, recipe, "PSASTRO.MAX.NREF");
+
+    // de-activate all files except PSASTRO.REFSTARS
+    pmFPAfileActivate (config->files, false, NULL);
+    pmFPAfileActivate (config->files, true, "PSASTRO.OUT.REFSTARS");
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	if (!chip->fromFPA) { continue; }
+
+        while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 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
+            // XXX there can only be one readout per chip in astrometry, right?
+            while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+                if (! readout->data_exists) { continue; }
+
+		psRegion *extent = pmReadoutExtent (readout);
+		if (!extent) {
+		    psError(PSASTRO_ERR_CONFIG, true, "Can't find readout size!\n");
+		    return NULL;
+		}
+
+		int Nx = abs(extent->x1 - extent->x0);
+		int Ny = abs(extent->y1 - extent->y0);
+
+                float minX = -fieldPadding*Nx;
+                float maxX = (1+fieldPadding)*Nx;
+                float minY = -fieldPadding*Ny;
+                float maxY = (1+fieldPadding)*Ny;
+
+                // the refstars is a subset within range of this chip
+                psArray *refstars = psArrayAllocEmpty (100);
+
+                // select the reference objects within range of this readout
+                // project the reference objects to this chip
+                for (int i = 0; i < refs->n; i++) {
+                    pmAstromObj *ref = pmAstromObjCopy(refs->data[index->data.S32[i]]);
+
+                    psProject (ref->TP, ref->sky, fpa->toSky);
+                    psPlaneTransformApply (ref->FP, fpa->fromTPA, ref->TP);
+                    psPlaneTransformApply (ref->chip, chip->fromFPA, ref->FP);
+
+                    // limit the X,Y range of the refs to the selected chip
+                    if (ref->chip->x < minX) goto skip;
+                    if (ref->chip->x > maxX) goto skip;
+                    if (ref->chip->y < minY) goto skip;
+                    if (ref->chip->y > maxY) goto skip;
+
+                    psArrayAdd (refstars, 100, ref);
+                skip:
+                    psFree (ref);
+
+		    if (nMax && (refstars->n >= nMax)) break;
+                }
+                psTrace ("psastro", 4, "Added %ld refstars\n", refstars->n);
+
+		psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSASTRO.REFSTARS", PS_DATA_ARRAY, "astrometry matches", refstars);
+		psFree (refstars);
+		psFree (extent);
+
+		if (matchLumFunc) {
+		    // in this case, no PSASTRO.REFSTARS is added to readout->analysis
+		    if (!psastroRefstarSubset (readout)) {
+			psError(PSASTRO_ERR_DATA, false, "Can't determine an appropriate refstar subset\n");
+			psFree (index);
+			psFree (view);
+			return false;
+		    }
+		}
+            }
+        }
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE;
+    }
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE;
+
+    // activate all files except PSASTRO.OUTPUT
+    pmFPAfileActivate (config->files, true, NULL);
+    pmFPAfileActivate (config->files, false, "PSASTRO.OUT.REFSTARS");
+
+    bool onlyRefstars = psMetadataLookupBool (&status, recipe, "PSASTRO.ONLY.REFSTARS");
+    if (onlyRefstars) exit (0);
+
+    psFree (index);
+    psFree (view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroCleanup.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroCleanup.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroCleanup.c	(revision 23321)
@@ -0,0 +1,30 @@
+/** @file psastroCleanup.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.8.2.1 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-19 17:59:50 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroStandAlone.h"
+
+void psastroCleanup (pmConfig *config) {
+
+    psFree (config);
+    pmVisualClose ();
+
+    psTimerStop ();
+    psMemCheckCorruption (stderr, true);
+    pmModelClassCleanup ();
+    psTimeFinalize ();
+    pmConceptsDone ();
+    pmConfigDone ();
+    fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, stdout, false), "psastro");
+    // fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, NULL, false), "psastro");
+
+    return;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroConvert.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroConvert.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroConvert.c	(revision 23321)
@@ -0,0 +1,190 @@
+/** @file psastroConvert.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.25 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+// leak free 2006.04.27
+
+bool psastroConvertFPA (pmFPA *fpa, psMetadata *recipe) {
+
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+        while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+            // process each of the readouts
+            while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+                if (! readout->data_exists) { continue; }
+
+                psastroConvertReadout (readout, recipe);
+            }
+        }
+    }
+    psFree (view);
+    return true;
+}
+
+// pass/apply the WCS information?
+bool psastroConvertReadout (pmReadout *readout, psMetadata *recipe) {
+
+    bool status;
+
+    // PSPHOT.SOURCES carries the pmSource objects (from psphot analysis or loaded externally)
+    psArray *sources = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.SOURCES");
+    if (sources == NULL)
+        return false;
+
+    // convert the pmSource objects into pmAstromObj objects (drop !STAR and SATSTAR?)
+    psArray *inStars = pmSourceToAstromObj (sources);
+
+    // sort in ascending magnitude order
+    // psArraySort (inStars, psastroSortByMag);
+    // psVector *index = psArraySortIndex (sources, pmSourceSortBySN);
+    psVector *index = psArraySortIndex (NULL, inStars, psastroSortByMag);
+
+    // XXX need to exit gracefully is inStars->n is 0 (or 1?)
+
+    // we are going to select the brighter Nmax subset for astrometry 
+    int nMax = psMetadataLookupS32 (&status, recipe, "PSASTRO.MAX.NRAW");
+    if (!status || !nMax) nMax = inStars->n;
+
+    // we are going to select the brighter Nmax subset for astrometry 
+    float iMagMax = psMetadataLookupF32 (&status, recipe, "PSASTRO.MAX.INST.MAG.RAW");
+    float iMagMin = psMetadataLookupF32 (&status, recipe, "PSASTRO.MIN.INST.MAG.RAW");
+
+    // we are going to select the brighter Nmax subset for astrometry 
+    pmSourceMode skip = PM_SOURCE_MODE_DEFAULT;
+    char *ignoreList = psMetadataLookupStr (&status, recipe, "PSASTRO.IGNORE");
+    if (ignoreList != NULL) {
+      psArray *list = psStringSplitArray (ignoreList, ",", false);
+      for (int i = 0; i < list->n; i++) {
+	pmSourceMode mode = pmSourceModeFromString (list->data[i]);
+	if (mode == PM_SOURCE_MODE_DEFAULT) {
+	  psWarning ("unknown source mode in PSASTRO.IGNORE, skipping");
+	  continue;
+	}
+	skip |= mode;
+      }
+      psFree (list);
+    }
+
+    // choose the first nMax sources
+    int j = 0;
+    psArray *rawStars = psArrayAlloc (PS_MIN (nMax, inStars->n));
+
+    float mMin = +100.0;
+    float mMax = -100.0;
+    int nModeSkip = 0;
+    int nFaintSkip = 0;
+    int nBrightSkip = 0;
+    int nInfSkip = 0;
+
+    for (int i = 0; (i < inStars->n) && (j < rawStars->n); i++) {
+	int n = index->data.S32[i];
+	pmSource *source = sources->data[n];
+
+        psTrace ("psastro", 6, "mag: %f +/- %f, mode: %x, skip: %x\n", source->psfMag, source->errMag, source->mode, skip);
+
+	if (source->mode & skip) { 
+	  nModeSkip ++;
+	  continue;
+	}
+
+	if ((iMagMax != 0.0) && (source->psfMag > iMagMax)) {
+	  nFaintSkip ++;
+	  continue;
+	}
+	if ((iMagMin != 0.0) && (source->psfMag < iMagMin)) {
+	  nBrightSkip ++;
+	  continue;
+	}
+	if (!isfinite(source->psfMag)) {
+	  nInfSkip ++;
+	  continue;
+	}
+	mMin = PS_MIN (mMin, source->psfMag);
+	mMax = PS_MAX (mMax, source->psfMag);
+	rawStars->data[j] = psMemIncrRefCounter (inStars->data[n]);
+	j++;
+    }
+    rawStars->n = j;
+
+    psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSASTRO.RAWSTARS", PS_DATA_ARRAY, "astrometry objects", rawStars);
+    psLogMsg ("psastro", 4, "loaded %ld sources, using %ld of %ld good sources (inst mag: %f to %f)\n", sources->n, rawStars->n, inStars->n, mMin, mMax);
+    psLogMsg ("psastro", 4, "skip reasons: mode: %d, faint: %d, bright: %d, inf: %d\n", nModeSkip, nFaintSkip, nBrightSkip, nInfSkip);
+
+    psFree (index);
+    psFree (inStars);
+    psFree (rawStars);
+
+    return true;
+}
+
+// select a magnitude range?
+psArray *pmSourceToAstromObj (psArray *sources) {
+
+    psArray *objects = psArrayAllocEmpty (sources->n);
+
+    for (int i = 0; i < sources->n; i++) {
+        pmSource *source = sources->data[i];
+
+        // only accept the PSF sources?
+	// XXX drop SATSTAR?
+        // if (source->type != PM_SOURCE_TYPE_STAR) continue;
+
+        pmModel *model = source->modelPSF;
+        if (model == NULL) continue;
+
+        psF32 *PAR = model->params->data.F32;
+
+        pmAstromObj *obj = pmAstromObjAlloc ();
+
+        // is the source magnitude calibrated in any sense?
+        obj->pix->x = PAR[PM_PAR_XPOS];
+        obj->pix->y = PAR[PM_PAR_YPOS];
+        obj->Mag = source->psfMag;
+
+        // XXX do we have the information giving the readout and cell offset?
+        // for the moment, assume chip == cell == readout
+        *obj->cell = *obj->pix;
+        *obj->chip = *obj->cell;
+
+        psArrayAdd (objects, 100, obj);
+        psFree (obj);
+    }
+    return objects;
+}
+
+/**
+ * sort by Mag (ascending)
+ */
+int psastroSortByMag (const void *a, const void *b)
+{
+    const pmAstromObj *A = a;
+    const pmAstromObj *B = b;
+
+    psF32 mA = (isfinite(A->Mag)) ? A->Mag : FLT_MAX;
+    psF32 mB = (isfinite(B->Mag)) ? B->Mag : FLT_MAX;
+
+    psF32 diff = mA - mB;
+    if (diff > +FLT_EPSILON) return (+1);
+    if (diff < -FLT_EPSILON) return (-1);
+    return (0);
+}
+
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroDataLoad.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroDataLoad.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroDataLoad.c	(revision 23321)
@@ -0,0 +1,90 @@
+/** @file psastroDataLoad.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.12 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroStandAlone.h"
+
+# define ESCAPE { \
+  psError(PS_ERR_UNKNOWN, false, "Failure in psastroDataLoad"); \
+  psFree (view); \
+  return false; \
+}
+  
+/**
+ * \brief this loop loads the data from the input files and selects the
+ * brighter stars for astrometry
+ *
+ * at the end of this function, the complete stellar data is loaded
+ * into the correct fpa structure locations (readout.analysis:PSPHOT.SOURCES)
+ * all of the different astrometry analysis modes use the same data load loop
+ */
+bool psastroDataLoad (pmConfig *config) {
+
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    psTimerStart ("psastro");
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe!\n");
+	return false;
+    }
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find input data!\n");
+	return false;
+    }
+
+    // de-activate all files except PSASTRO.INPUT
+    pmFPAfileActivate (config->files, false, NULL);
+    pmFPAfileActivate (config->files, true, "PSASTRO.INPUT");
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+
+    while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+
+	while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+            psTrace ("psastro", 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)) ESCAPE;
+
+	    // process each of the readouts
+	    while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+		if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+		if (!readout->data_exists) { continue; }
+
+		if (!psastroConvertReadout (readout, recipe)) ESCAPE;
+
+		if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE;
+	    }
+	    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE;
+	}
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE;
+    }
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE;
+
+    psLogMsg ("psastro", 3, "load data : %f sec\n", psTimerMark ("psastro"));
+
+    psFree (view);
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroDataSave.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroDataSave.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroDataSave.c	(revision 23321)
@@ -0,0 +1,94 @@
+/** @file psastroDataSave.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.15 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+# define ESCAPE { \
+  psError(PS_ERR_UNKNOWN, false, "Failure in psastroDataSave"); \
+  psFree (view); \
+  return false; \
+}
+
+/**
+ * this loop saves the photometry/astrometry data files
+ */
+bool psastroDataSave (pmConfig *config) {
+
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe!\n");
+        return false;
+    }
+
+    // select the output data sources
+    pmFPAfile *output = psMetadataLookupPtr (NULL, config->files, "PSASTRO.OUTPUT");
+    if (!output) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find or interpret output file rule PSASTRO.OUTPUT!\n");
+        return false;
+    }
+
+    // de-activate all files except PSASTRO.OUTPUT
+    pmFPAfileActivate (config->files, false, NULL);
+    pmFPAfileActivate (config->files, true, "PSASTRO.OUTPUT");
+    pmFPAfileActivate (config->files, true, "PSASTRO.OUT.ASTROM");
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmHDU *lastHDU = NULL;              // Last HDU updated
+
+    // open/load files as needed
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+
+    while ((chip = pmFPAviewNextChip (view, output->fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+
+        while ((cell = pmFPAviewNextCell (view, output->fpa, 1)) != NULL) {
+            psTrace ("psastro", 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)) ESCAPE;
+
+            // process each of the readouts
+            while ((readout = pmFPAviewNextReadout (view, output->fpa, 1)) != NULL) {
+                if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+                if (!readout->data_exists) { continue; }
+
+                // Put version information into the header
+                pmHDU *hdu = pmHDUGetHighest(output->fpa, chip, cell);
+                if (hdu && hdu != lastHDU) {
+                    psastroVersionHeaderFull(hdu->header);
+                    lastHDU = hdu;
+                }
+
+                if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE;
+            }
+            if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE;
+        }
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE;
+    }
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE;
+
+    // Write out summary statistics
+    if (!psastroMetadataStats (config)) ESCAPE;
+
+    // activate all files except PSASTRO.OUTPUT
+    pmFPAfileActivate (config->files, true, NULL);
+    pmFPAfileActivate (config->files, false, "PSASTRO.OUTPUT");
+
+    psFree (view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroDefineFiles.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroDefineFiles.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroDefineFiles.c	(revision 23321)
@@ -0,0 +1,164 @@
+/** @file psastroDefineFiles.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.10 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+bool psastroDefineFiles (pmConfig *config, pmFPAfile *input) {
+
+    // these calls bind the I/O handle to the specified fpa
+    bool status;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+	psError(PSASTRO_ERR_CONFIG, false, "Can't find PSASTRO recipe!\n");
+	return false;
+    }
+
+    pmFPAfile *output = pmFPAfileDefineOutput (config, input->fpa, "PSASTRO.OUTPUT");
+    if (!output) {
+	psError(PSASTRO_ERR_CONFIG, false, "Failed to build FPA from PSASTRO.INPUT");
+	return false;
+    }
+    output->save = true;
+
+    bool fixChips = psMetadataLookupBool (&status, config->arguments, "PSASTRO.FIX.CHIPS");
+    if (!status) {
+	fixChips = psMetadataLookupBool (&status, recipe, "PSASTRO.FIX.CHIPS");
+    }
+    bool useModel = psMetadataLookupBool (&status, config->arguments, "PSASTRO.USE.MODEL");
+    if (!status) {
+	fixChips = psMetadataLookupBool (&status, recipe, "PSASTRO.USE.MODEL");
+    }
+    if (fixChips || useModel) {
+        if (!psastroDefineFile (config, input->fpa, "PSASTRO.MODEL", "ASTROM.MODEL", PM_FPA_FILE_ASTROM_MODEL, PM_DETREND_TYPE_ASTROM)) {
+            psError (PS_ERR_IO, false, "Can't find an astrometry model file");
+            return NULL;
+        }
+    }
+
+    // this step is optional
+    bool REFSTAR_MASK = psMetadataLookupBool (&status, recipe, "REFSTAR_MASK");
+    if (REFSTAR_MASK) {
+
+        if (!psastroDefineFile (config, input->fpa, "PSASTRO.REFMASK", "REFMASK", PM_FPA_FILE_MASK, PM_DETREND_TYPE_MASK)) {
+            psError (PS_ERR_IO, false, "Can't find an input mask file");
+            return NULL;
+        }
+
+        if (!psastroDefineFile (config, input->fpa, "PSASTRO.INPUT.MASK", "INPUT.MASK", PM_FPA_FILE_MASK, PM_DETREND_TYPE_MASK)) {
+            psError (PS_ERR_IO, false, "Can't find an input mask file");
+            return NULL;
+        }
+
+	pmFPAfile *inMask = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT.MASK");
+	if (!inMask) {
+            psError (PS_ERR_IO, false, "Can't find an input mask file");
+            return NULL;
+        }
+
+	// XXX not yet sure if we need to mosaic or not...
+	pmFPAfile *outMask = pmFPAfileDefineOutputFromFile  (config, inMask, "PSASTRO.OUTPUT.MASK");
+	// pmFPAfile *outMask = pmFPAfileDefineChipMosaic(config, inMask->fpa, "PSASTRO.OUTPUT.MASK");
+	if (!outMask) {
+	    psError (PS_ERR_IO, false, "Can't find the astrometry refstars file definition");
+	    return NULL;
+	}
+	if (outMask->type != PM_FPA_FILE_MASK) {
+	    psError(PS_ERR_IO, true, "%s is not of type %s", "PSASTRO.OUTPUT.MASK", pmFPAfileStringFromType (PM_FPA_FILE_MASK));
+	    return NULL;
+	}
+	outMask->save = true;
+	inMask->freeLevel = outMask->dataLevel;
+    }
+
+    bool saveRefstars = psMetadataLookupBool (&status, config->arguments, "PSASTRO.SAVE.REFSTARS");
+    if (!status) {
+      saveRefstars = psMetadataLookupBool (&status, recipe, "PSASTRO.SAVE.REFSTARS");
+    }
+    if (saveRefstars) {
+	// look for the file in the camera config table
+	pmFPAfile *file = pmFPAfileDefineOutputFromFile  (config, input, "PSASTRO.OUT.REFSTARS");
+	if (!file) {
+	    psError (PS_ERR_IO, false, "Can't find the astrometry refstars file definition");
+	    return NULL;
+	}
+	if (file->type != PM_FPA_FILE_ASTROM_REFSTARS) {
+	    psError(PS_ERR_IO, true, "%s is not of type %s", "PSASTRO.OUT.REFSTARS", pmFPAfileStringFromType (PM_FPA_FILE_ASTROM_REFSTARS));
+	    return NULL;
+	}
+	file->save = true;
+    }
+
+
+# if (0)
+    // optionally save output plots
+    if (!pmFPAfileDefineOutput (config, input->fpa, "SOURCE.PLOT.MOMENTS")) {
+	psTrace ("psphot", 3, "Cannot find a rule for SOURCE.PLOT.MOMENTS");
+    }
+    if (!pmFPAfileDefineOutput (config, input->fpa, "SOURCE.PLOT.PSFMODEL")) {
+	psTrace ("psphot", 3, "Cannot find a rule for SOURCE.PLOT.PSFMODEL");
+    }
+# endif
+
+    return true;
+}
+
+bool psastroDefineFile (pmConfig *config, pmFPA *input, char *filerule, char *argname, pmFPAfileType fileType, pmDetrendType detrendType) {
+
+    bool status;
+    pmFPAfile *file;
+
+    // look for the file on the argument list
+    file = pmFPAfileDefineFromArgs  (&status, config, filerule, argname);
+    if (!status) {
+	psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+	return false;
+    }
+    if (file) {
+	if (file->type != fileType) {
+	    psError(PS_ERR_IO, true, "%s is not of type %s", filerule, pmFPAfileStringFromType (fileType));
+	    return false;
+	}
+	return true;
+    }
+
+    // look for the file in the camera config table
+    file = pmFPAfileDefineFromConf  (&status, config, filerule);
+    if (!status) {
+	psError (PS_ERR_UNKNOWN, false, "failed to load find definition");
+	return false;
+    }
+    if (file) {
+	if (file->type != fileType) {
+	    psError(PS_ERR_IO, true, "%s is not of type %s", filerule, pmFPAfileStringFromType (fileType));
+	    return false;
+	}
+	return true;
+    }
+
+    // look for the file to be loaded from the detrend database
+    file = pmFPAfileDefineFromDetDB (&status, config, filerule, input, detrendType);
+    if (!status) {
+	psError (PS_ERR_UNKNOWN, false, "failed to load file definition");
+	return false;
+    }
+    if (file) {
+	if (file->type != fileType) {
+	    psError(PS_ERR_IO, true, "%s is not of type %s", filerule, pmFPAfileStringFromType (fileType));
+	    return false;
+	}
+	return true;
+    }
+    return false;
+}
+
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroDemoDump.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroDemoDump.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroDemoDump.c	(revision 23321)
@@ -0,0 +1,319 @@
+/** @file psastroMosaicDemoDump.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.11 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+/**
+ * this function is used for test purposes 
+ * (-trace psastro.dump.psastroAstromGuess 1)
+ */
+bool psastroDumpStars (psArray *stars, char *filename) {
+
+    FILE *f = fopen (filename, "w");
+
+    for (int i = 0; i < stars->n; i++) {
+	pmAstromObj *obj = stars->data[i];
+
+	// write out the upward projections
+	fprintf (f, "%d  %f %f  %f  %f %f  %f %f  %f %f\n", i,
+		 obj->sky->r, obj->sky->d, obj->Mag, 
+		 obj->TP->x, obj->TP->y, 
+		 obj->FP->x, obj->FP->y, 
+		 obj->chip->x, obj->chip->y);
+    }
+    fclose (f);
+    return true;
+}
+
+/** this function is used for test purposes 
+ * (-trace psastro.dump.psastroAstromGuess 1)
+ */
+bool psastroDumpRawstars (psArray *rawstars, pmFPA *fpa, pmChip *chip) {
+
+    char *filename = NULL;
+    char *chipname = psMetadataLookupStr (NULL, chip->concepts, "CHIP.NAME");
+
+    psStringAppend (&filename, "rawstars.up.%s.dat", chipname);
+    FILE *f1 = fopen (filename, "w");
+    psFree (filename);
+    filename = NULL;
+
+    psStringAppend (&filename, "rawstars.dn.%s.dat", chipname);
+    FILE *f2 = fopen (filename, "w");
+    psFree (filename);
+    filename = NULL;
+
+    for (int i = 0; i < rawstars->n; i++) {
+	pmAstromObj *raw = rawstars->data[i];
+
+	psPlane *fp = psPlaneAlloc();
+	psPlane *tp = psPlaneAlloc();
+	psPlane *ch = psPlaneAlloc();
+			
+	psProject (tp, raw->sky, fpa->toSky);
+	psPlaneTransformApply (fp, fpa->fromTPA, tp);
+	psPlaneTransformApply (ch, chip->fromFPA, fp);
+			
+	// write out the upward projections
+	fprintf (f1, "%d  %f %f  %f  %f %f  %f %f  %f %f\n", i,
+		 raw->sky->r, raw->sky->d, raw->Mag, 
+		 raw->TP->x, raw->TP->y, 
+		 raw->FP->x, raw->FP->y, 
+		 raw->chip->x, raw->chip->y);
+		
+	// write out the downward projections
+	fprintf (f2, "%d  %f %f  %f  %f %f  %f %f  %f %f\n", i,
+		 raw->sky->r, raw->sky->d, raw->Mag, 
+		 tp->x, tp->y, 
+		 fp->x, fp->y, 
+		 ch->x, ch->y);
+		
+	psFree (fp);
+	psFree (tp);
+	psFree (ch);
+    }
+
+    fclose (f1);
+    fclose (f2);
+    return true;
+}
+
+bool psastroDumpMatchedStars (char *filename, psArray *rawstars, psArray *refstars, psArray *match) {
+    
+    FILE *f = fopen (filename, "w");
+
+    for (int i = 0; i < match->n; i++) {
+        pmAstromMatch *pair = match->data[i];
+        pmAstromObj *raw = rawstars->data[pair->raw];
+        pmAstromObj *ref = refstars->data[pair->ref];
+
+	fprintf (f, "%f %f  %f %f  %f %f  %f %f  %f   |   ",  
+		 DEG_RAD*raw->sky->r, DEG_RAD*raw->sky->d, 
+		 raw->TP->x, raw->TP->y, 
+		 raw->FP->x, raw->FP->y, 
+		 raw->chip->x, raw->chip->y, raw->Mag);
+
+	fprintf (f, "%f %f  %f %f  %f %f  %f %f  %f\n", 
+		 DEG_RAD*ref->sky->r, DEG_RAD*ref->sky->d, 
+		 ref->TP->x, ref->TP->y, 
+		 ref->FP->x, ref->FP->y, 
+		 ref->chip->x, ref->chip->y, ref->Mag);
+    }
+    fclose (f);
+
+    return true;
+}
+
+/**
+ * this function is used for test purposes 
+ * (-trace psastro.dump.psastroLoadRefstars 1)
+ */
+bool psastroDumpRefstars (psArray *refstars, char *filename) {
+
+    FILE *f = fopen (filename, "w");
+
+    for (int i = 0; i < refstars->n; i++) {
+	pmAstromObj *ref = refstars->data[i];
+
+	// write out the refstar data
+	fprintf (f, "%d  %f %f  %f\n", i,
+		 ref->sky->r, ref->sky->d, ref->Mag);
+    }
+
+    fclose (f);
+    return true;
+}
+
+bool psastroDumpMatches (pmFPA *fpa, char *filename) {
+
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    FILE *f = fopen (filename, "w");
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) continue;
+	
+	char *chipName = psMetadataLookupStr(NULL, chip->concepts, "CHIP.NAME");
+
+	while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 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
+	    // XXX there can only be one readout per chip, right?
+	    while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+		if (! readout->data_exists) continue;
+
+		// select the raw objects for this readout
+		psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+		if (rawstars == NULL) continue;
+
+		// select the raw objects for this readout
+		psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+		if (refstars == NULL) continue;
+		psTrace ("psastro", 4, "Trying %ld refstars\n", refstars->n);
+
+		psArray *matches = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.MATCH");
+		if (matches == NULL) continue;
+
+		for (int i = 0; i < matches->n; i++) {
+		    pmAstromMatch *match = matches->data[i];
+
+		    pmAstromObj *raw = rawstars->data[match->raw];
+		    fprintf (f, "%s  %f %f  %f %f  %f %f  %f %f  %f   |   ",  
+			     chipName, DEG_RAD*raw->sky->r, DEG_RAD*raw->sky->d, 
+			     raw->TP->x, raw->TP->y, 
+			     raw->FP->x, raw->FP->y, 
+			     raw->chip->x, raw->chip->y, raw->Mag);
+
+		    pmAstromObj *ref = refstars->data[match->ref];
+		    fprintf (f, "%f %f  %f %f  %f %f  %f %f  %f\n", 
+			     DEG_RAD*ref->sky->r, DEG_RAD*ref->sky->d, 
+			     ref->TP->x, ref->TP->y, 
+			     ref->FP->x, ref->FP->y, 
+			     ref->chip->x, ref->chip->y, ref->Mag);
+		}
+	    }
+	}
+    }
+    fclose (f);
+    psFree (view);
+    return true;
+}
+
+/**
+ * this function is used for test purposes (-trace psastro.dump 1)
+ */
+bool psastroDumpGradients (psArray *gradients, char *filename) {
+
+    FILE *f = fopen (filename, "w");
+
+    for (int i = 0; i < gradients->n; i++) {
+	pmAstromGradient *gradient = gradients->data[i];
+
+	// write out the refstar data
+	fprintf (f, "%d  %f %f   %f %f  %f %f\n", i,
+		 gradient->FP.x, gradient->FP.y, 
+		 gradient->dTPdL.x, gradient->dTPdL.y, 
+		 gradient->dTPdM.x, gradient->dTPdM.y);
+    }
+
+    fclose (f);
+    return true;
+}
+
+bool psastroDumpCorners (char *filenameU, char *filenameD, pmFPA *fpa) {
+
+  // XXX test output of chip corners based on model
+  FILE *fu = fopen (filenameU, "w");
+  FILE *fd = fopen (filenameD, "w");
+
+  pmFPAview *view = pmFPAviewAlloc (0);
+
+  float fpaAngle = PM_DEG_RAD * atan2 (fpa->toTPA->y->coeff[1][0], fpa->toTPA->x->coeff[1][0]);
+
+  fprintf (fu, "# boresite: %f, %f @ %f\n", fpa->toSky->R*PS_DEG_RAD, fpa->toSky->D*PS_DEG_RAD, fpaAngle);
+  fprintf (fd, "# boresite: %f, %f @ %f\n", fpa->toSky->R*PS_DEG_RAD, fpa->toSky->D*PS_DEG_RAD, fpaAngle);
+
+  pmChip *chip = NULL;
+  while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        if (!chip->process || !chip->file_exists || !chip->data_exists) { continue; }
+
+	// XXX write out the four corners for a test
+	psRegion *region = pmChipPixels (chip);
+	psPlane ptCP, ptFP, ptTP;
+	psSphere ptSky;
+
+	// UP 0,0
+	ptCP.x = region->x0; ptCP.y = region->y0;
+	psPlaneTransformApply (&ptFP, chip->toFPA, &ptCP);
+	psPlaneTransformApply (&ptTP, fpa->toTPA, &ptFP);
+	psDeproject (&ptSky, &ptTP, fpa->toSky);
+	fprintf (fu, "%10.6f %10.6f  %8.1f %8.1f  %8.1f %8.1f  %8.1f %8.1f\n", ptSky.r, ptSky.d, ptTP.x, ptTP.y, ptFP.x, ptFP.y, ptCP.x, ptCP.y);
+
+	// DOWN 0,0
+	psProject (&ptTP, &ptSky, fpa->toSky);
+	psPlaneTransformApply (&ptFP, fpa->fromTPA, &ptTP);
+	psPlaneTransformApply (&ptCP, chip->fromFPA, &ptFP);
+	fprintf (fd, "%10.6f %10.6f  %8.1f %8.1f  %8.1f %8.1f  %8.1f %8.1f\n", ptSky.r, ptSky.d, ptTP.x, ptTP.y, ptFP.x, ptFP.y, ptCP.x, ptCP.y);
+
+	// UP 1,0
+	ptCP.x = region->x1; ptCP.y = region->y0;
+	psPlaneTransformApply (&ptFP, chip->toFPA, &ptCP);
+	psPlaneTransformApply (&ptTP, fpa->toTPA, &ptFP);
+	psDeproject (&ptSky, &ptTP, fpa->toSky);
+	fprintf (fu, "%10.6f %10.6f  %8.1f %8.1f  %8.1f %8.1f  %8.1f %8.1f\n", ptSky.r, ptSky.d, ptTP.x, ptTP.y, ptFP.x, ptFP.y, ptCP.x, ptCP.y);
+	fprintf (fu, "%10.6f %10.6f  %8.1f %8.1f  %8.1f %8.1f  %8.1f %8.1f\n", ptSky.r, ptSky.d, ptTP.x, ptTP.y, ptFP.x, ptFP.y, ptCP.x, ptCP.y);
+
+	// DOWN 1,0
+	psProject (&ptTP, &ptSky, fpa->toSky);
+	psPlaneTransformApply (&ptFP, fpa->fromTPA, &ptTP);
+	psPlaneTransformApply (&ptCP, chip->fromFPA, &ptFP);
+	fprintf (fd, "%10.6f %10.6f  %8.1f %8.1f  %8.1f %8.1f  %8.1f %8.1f\n", ptSky.r, ptSky.d, ptTP.x, ptTP.y, ptFP.x, ptFP.y, ptCP.x, ptCP.y);
+	fprintf (fd, "%10.6f %10.6f  %8.1f %8.1f  %8.1f %8.1f  %8.1f %8.1f\n", ptSky.r, ptSky.d, ptTP.x, ptTP.y, ptFP.x, ptFP.y, ptCP.x, ptCP.y);
+
+	// UP 1,1
+	ptCP.x = region->x1; ptCP.y = region->y1;
+	psPlaneTransformApply (&ptFP, chip->toFPA, &ptCP);
+	psPlaneTransformApply (&ptTP, fpa->toTPA, &ptFP);
+	psDeproject (&ptSky, &ptTP, fpa->toSky);
+	fprintf (fu, "%10.6f %10.6f  %8.1f %8.1f  %8.1f %8.1f  %8.1f %8.1f\n", ptSky.r, ptSky.d, ptTP.x, ptTP.y, ptFP.x, ptFP.y, ptCP.x, ptCP.y);
+	fprintf (fu, "%10.6f %10.6f  %8.1f %8.1f  %8.1f %8.1f  %8.1f %8.1f\n", ptSky.r, ptSky.d, ptTP.x, ptTP.y, ptFP.x, ptFP.y, ptCP.x, ptCP.y);
+
+	// DOWN 1,1
+	psProject (&ptTP, &ptSky, fpa->toSky);
+	psPlaneTransformApply (&ptFP, fpa->fromTPA, &ptTP);
+	psPlaneTransformApply (&ptCP, chip->fromFPA, &ptFP);
+	fprintf (fd, "%10.6f %10.6f  %8.1f %8.1f  %8.1f %8.1f  %8.1f %8.1f\n", ptSky.r, ptSky.d, ptTP.x, ptTP.y, ptFP.x, ptFP.y, ptCP.x, ptCP.y);
+	fprintf (fd, "%10.6f %10.6f  %8.1f %8.1f  %8.1f %8.1f  %8.1f %8.1f\n", ptSky.r, ptSky.d, ptTP.x, ptTP.y, ptFP.x, ptFP.y, ptCP.x, ptCP.y);
+
+	// UP 0,1
+	ptCP.x = region->x0; ptCP.y = region->y1;
+	psPlaneTransformApply (&ptFP, chip->toFPA, &ptCP);
+	psPlaneTransformApply (&ptTP, fpa->toTPA, &ptFP);
+	psDeproject (&ptSky, &ptTP, fpa->toSky);
+	fprintf (fu, "%10.6f %10.6f  %8.1f %8.1f  %8.1f %8.1f  %8.1f %8.1f\n", ptSky.r, ptSky.d, ptTP.x, ptTP.y, ptFP.x, ptFP.y, ptCP.x, ptCP.y);
+	fprintf (fu, "%10.6f %10.6f  %8.1f %8.1f  %8.1f %8.1f  %8.1f %8.1f\n", ptSky.r, ptSky.d, ptTP.x, ptTP.y, ptFP.x, ptFP.y, ptCP.x, ptCP.y);
+
+	// DOWN 0,1
+	psProject (&ptTP, &ptSky, fpa->toSky);
+	psPlaneTransformApply (&ptFP, fpa->fromTPA, &ptTP);
+	psPlaneTransformApply (&ptCP, chip->fromFPA, &ptFP);
+	fprintf (fd, "%10.6f %10.6f  %8.1f %8.1f  %8.1f %8.1f  %8.1f %8.1f\n", ptSky.r, ptSky.d, ptTP.x, ptTP.y, ptFP.x, ptFP.y, ptCP.x, ptCP.y);
+	fprintf (fd, "%10.6f %10.6f  %8.1f %8.1f  %8.1f %8.1f  %8.1f %8.1f\n", ptSky.r, ptSky.d, ptTP.x, ptTP.y, ptFP.x, ptFP.y, ptCP.x, ptCP.y);
+
+	// UP 0,0
+	ptCP.x = region->x0; ptCP.y = region->y0;
+	psPlaneTransformApply (&ptFP, chip->toFPA, &ptCP);
+	psPlaneTransformApply (&ptTP, fpa->toTPA, &ptFP);
+	psDeproject (&ptSky, &ptTP, fpa->toSky);
+	fprintf (fu, "%10.6f %10.6f  %8.1f %8.1f  %8.1f %8.1f  %8.1f %8.1f\n", ptSky.r, ptSky.d, ptTP.x, ptTP.y, ptFP.x, ptFP.y, ptCP.x, ptCP.y);
+
+	// DOWN 0,0
+	psProject (&ptTP, &ptSky, fpa->toSky);
+	psPlaneTransformApply (&ptFP, fpa->fromTPA, &ptTP);
+	psPlaneTransformApply (&ptCP, chip->fromFPA, &ptFP);
+	fprintf (fd, "%10.6f %10.6f  %8.1f %8.1f  %8.1f %8.1f  %8.1f %8.1f\n", ptSky.r, ptSky.d, ptTP.x, ptTP.y, ptFP.x, ptFP.y, ptCP.x, ptCP.y);
+
+	psFree (region);
+  }
+
+  fclose (fu);
+  fclose (fd);
+  psFree (view);
+  return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroDemoPlot.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroDemoPlot.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroDemoPlot.c	(revision 23321)
@@ -0,0 +1,526 @@
+/** @file psastroMosaicDemoPlot.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.12 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+# if (HAVE_KAPA)
+# include <kapa.h>
+
+bool pmKapaPlotVectorTriple_AutoLimitsZscale_OpenGraph (int kapa, Graphdata *graphdata, psVector *xVec, psVector *yVec, psVector *zVec, bool increasing);
+
+bool psastroPlotRawstars (psArray *rawstars, pmFPA *fpa, pmChip *chip, psMetadata *recipe)
+{
+    Graphdata graphdata;
+    KapaSection section;
+
+    int kapa = pmKapaOpen (true);
+    if (kapa == -1) {
+        psError(PS_ERR_UNKNOWN, true, "failure to open kapa");
+        return false;
+    }
+
+    bool status = false;
+    float iMagMin = psMetadataLookupF32 (&status, recipe, "PSASTRO.PLOT.INST.MAG.MIN");
+    float iMagMax = psMetadataLookupF32 (&status, recipe, "PSASTRO.PLOT.INST.MAG.MAX");
+
+    KapaResize (kapa, 1000, 1000);
+    KapaInitGraph (&graphdata);
+    KapaClearPlots (kapa);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 7;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+
+    section.dx = 0.5;
+    section.dy = 0.5;
+
+    psVector *xVec = psVectorAlloc (rawstars->n, PS_TYPE_F32);
+    psVector *yVec = psVectorAlloc (rawstars->n, PS_TYPE_F32);
+    psVector *zVec = psVectorAlloc (rawstars->n, PS_TYPE_F32);
+
+    section.x = 0.0;
+    section.y = 0.5;
+    section.name = NULL;
+    psStringAppend (&section.name, "a0");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    int n = 0;
+    for (int i = 0; i < rawstars->n; i++) {
+        pmAstromObj *raw = rawstars->data[i];
+        if (!isfinite(raw->Mag)) continue;
+        if (raw->Mag < iMagMin) continue;
+        if (raw->Mag > iMagMax) continue;
+
+        xVec->data.F32[n] = raw->chip->x;
+        yVec->data.F32[n] = raw->chip->y;
+        zVec->data.F32[n] = raw->Mag;
+        n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    section.x = 0.5;
+    section.y = 0.5;
+    section.name = NULL;
+    psStringAppend (&section.name, "a1");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    n = 0;
+    for (int i = 0; i < rawstars->n; i++) {
+        pmAstromObj *raw = rawstars->data[i];
+        if (!isfinite(raw->Mag)) continue;
+        if (raw->Mag < iMagMin) continue;
+        if (raw->Mag > iMagMax) continue;
+
+        xVec->data.F32[n] = raw->FP->x;
+        yVec->data.F32[n] = raw->FP->y;
+        zVec->data.F32[n] = raw->Mag;
+        n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    section.x = 0.0;
+    section.y = 0.0;
+    section.name = NULL;
+    psStringAppend (&section.name, "a2");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    n = 0;
+    for (int i = 0; i < rawstars->n; i++) {
+        pmAstromObj *raw = rawstars->data[i];
+        if (!isfinite(raw->Mag)) continue;
+        if (raw->Mag < iMagMin) continue;
+        if (raw->Mag > iMagMax) continue;
+
+        xVec->data.F32[n] = raw->TP->x;
+        yVec->data.F32[n] = raw->TP->y;
+        zVec->data.F32[n] = raw->Mag;
+        n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    section.x = 0.5;
+    section.y = 0.0;
+    section.name = NULL;
+    psStringAppend (&section.name, "a3");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    n = 0;
+    for (int i = 0; i < rawstars->n; i++) {
+        pmAstromObj *raw = rawstars->data[i];
+        if (!isfinite(raw->Mag)) continue;
+        if (raw->Mag < iMagMin) continue;
+        if (raw->Mag > iMagMax) continue;
+
+        xVec->data.F32[n] = DEG_RAD*raw->sky->r;
+        yVec->data.F32[n] = DEG_RAD*raw->sky->d;
+        zVec->data.F32[n] = raw->Mag;
+        n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    // flip x (East increase to left)
+    SWAP (graphdata.xmin, graphdata.xmax);
+    KapaSetLimits (kapa, &graphdata);
+
+    // pause and wait for user input:
+    // continue, save (provide name), ??
+    char key[10], name[80];
+    fprintf (stdout, "(s)ave plot or [c]ontinue? ");
+    if (!fgets(key, 8, stdin)) {
+        psWarning("Unable to read option");
+    } else if (key[0] == 's') {
+        fprintf (stdout, "enter plot name [rawstars.png]: ");
+        if (fscanf(stdin, "%s", name) != 1) {
+            psWarning("Unable to read plot name");
+        } else if (!strcmp (name, "")) {
+            strcpy (name, "rawstars.png");
+        }
+        KapaPNG (kapa, name);
+    }
+
+    psFree (xVec);
+    psFree (yVec);
+    psFree (zVec);
+    return true;
+}
+
+bool psastroPlotRefstars (psArray *refstars, psMetadata *recipe)
+{
+    Graphdata graphdata;
+
+    int kapa = pmKapaOpen (true);
+    if (kapa == -1) {
+        psError(PS_ERR_UNKNOWN, true, "failure to open kapa");
+        return false;
+    }
+
+    bool status = false;
+    float rMagMin = psMetadataLookupF32 (&status, recipe, "PSASTRO.PLOT.REF.MAG.MIN");
+    float rMagMax = psMetadataLookupF32 (&status, recipe, "PSASTRO.PLOT.REF.MAG.MAX");
+
+    KapaResize (kapa, 1000, 1000);
+    KapaInitGraph (&graphdata);
+    KapaClearSections (kapa);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 7;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+
+    psVector *xVec = psVectorAlloc (refstars->n, PS_TYPE_F32);
+    psVector *yVec = psVectorAlloc (refstars->n, PS_TYPE_F32);
+    psVector *zVec = psVectorAlloc (refstars->n, PS_TYPE_F32);
+
+    int n = 0;
+    for (int i = 0; i < refstars->n; i++) {
+        pmAstromObj *ref = refstars->data[i];
+        if (!isfinite(ref->Mag)) continue;
+        if (ref->Mag > rMagMax) continue;
+        if (ref->Mag < rMagMin) continue;
+
+        xVec->data.F32[n] = DEG_RAD*ref->sky->r;
+        yVec->data.F32[n] = DEG_RAD*ref->sky->d;
+        zVec->data.F32[n] = ref->Mag;
+        n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    // flip x (East increase to left)
+    SWAP (graphdata.xmin, graphdata.xmax);
+    KapaSetLimits (kapa, &graphdata);
+
+    // pause and wait for user input:
+    // continue, save (provide name), ??
+    char key[10], name[80];
+    fprintf (stdout, "(s)ave plot or [c]ontinue? ");
+    if (!fgets(key, 8, stdin)) {
+        psWarning("Couldn't read anything.");
+    } else if (key[0] == 's') {
+        fprintf (stdout, "enter plot name [refstars.png]: ");
+        if (fscanf (stdin, "%s", name) != 1) {
+            psWarning("Unable to read name");
+        } else if (!strcmp (name, "")) {
+            strcpy (name, "refstars.png");
+        }
+        KapaPNG (kapa, name);
+    }
+
+    psFree (xVec);
+    psFree (yVec);
+    psFree (zVec);
+    return true;
+}
+
+bool psastroPlotOneChipFit (psArray *rawstars, psArray *refstars, psArray *match, psMetadata *recipe) {
+
+    Graphdata graphdata;
+    KapaSection section;
+
+    int kapa = pmKapaOpen (true);
+    if (kapa == -1) {
+        psError(PS_ERR_UNKNOWN, true, "failure to open kapa");
+        return false;
+    }
+
+    bool status = false;
+    float iMagMin = psMetadataLookupF32 (&status, recipe, "PSASTRO.PLOT.INST.MAG.MIN");
+    float iMagMax = psMetadataLookupF32 (&status, recipe, "PSASTRO.PLOT.INST.MAG.MAX");
+    float rMagMin = psMetadataLookupF32 (&status, recipe, "PSASTRO.PLOT.REF.MAG.MIN");
+    float rMagMax = psMetadataLookupF32 (&status, recipe, "PSASTRO.PLOT.REF.MAG.MAX");
+
+    KapaResize (kapa, 1000, 1000);
+    KapaInitGraph (&graphdata);
+    KapaClearPlots (kapa);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 7;
+    graphdata.size = 0.5;
+    graphdata.style = 2;
+
+    section.dx = 0.5;
+    section.dy = 0.5;
+
+    psVector *xVec = psVectorAlloc (match->n, PS_TYPE_F32);
+    psVector *yVec = psVectorAlloc (match->n, PS_TYPE_F32);
+    psVector *zVec = psVectorAlloc (match->n, PS_TYPE_F32);
+
+    // X vs dX
+    section.x = 0.0;
+    section.y = 0.5;
+    section.name = NULL;
+    psStringAppend (&section.name, "a0");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    int n = 0;
+    for (int i = 0; i < match->n; i++) {
+        pmAstromMatch *pair = match->data[i];
+        pmAstromObj *raw = rawstars->data[pair->raw];
+        pmAstromObj *ref = refstars->data[pair->ref];
+
+        if (raw->Mag < iMagMin) continue;
+        if (raw->Mag > iMagMax) continue;
+        if (ref->Mag < rMagMin) continue;
+        if (ref->Mag > rMagMax) continue;
+
+        xVec->data.F32[n] = raw->chip->x;
+        yVec->data.F32[n] = raw->chip->x - ref->chip->x;
+        zVec->data.F32[n] = raw->Mag;
+        n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    // X vs dY
+    section.x = 0.5;
+    section.y = 0.5;
+    section.name = NULL;
+    psStringAppend (&section.name, "a1");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    n = 0;
+    for (int i = 0; i < match->n; i++) {
+        pmAstromMatch *pair = match->data[i];
+        pmAstromObj *raw = rawstars->data[pair->raw];
+        pmAstromObj *ref = refstars->data[pair->ref];
+
+        if (!isfinite(raw->Mag)) continue;
+        if (raw->Mag < iMagMin) continue;
+        if (raw->Mag > iMagMax) continue;
+        if (ref->Mag < rMagMin) continue;
+        if (ref->Mag > rMagMax) continue;
+
+        xVec->data.F32[n] = raw->chip->x;
+        yVec->data.F32[n] = raw->chip->y - ref->chip->y;
+        zVec->data.F32[n] = raw->Mag;
+        n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    // Y vs dX
+    section.x = 0.0;
+    section.y = 0.0;
+    section.name = NULL;
+    psStringAppend (&section.name, "a2");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    n = 0;
+    for (int i = 0; i < match->n; i++) {
+        pmAstromMatch *pair = match->data[i];
+        pmAstromObj *raw = rawstars->data[pair->raw];
+        pmAstromObj *ref = refstars->data[pair->ref];
+
+        if (!isfinite(raw->Mag)) continue;
+        if (raw->Mag < iMagMin) continue;
+        if (raw->Mag > iMagMax) continue;
+        if (ref->Mag < rMagMin) continue;
+        if (ref->Mag > rMagMax) continue;
+
+        xVec->data.F32[n] = raw->chip->y;
+        yVec->data.F32[n] = raw->chip->x - ref->chip->x;
+        zVec->data.F32[n] = raw->Mag;
+        n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    // Y vs dY
+    section.x = 0.5;
+    section.y = 0.0;
+    section.name = NULL;
+    psStringAppend (&section.name, "a3");
+    KapaSetSection (kapa, &section);
+    psFree (section.name);
+
+    n = 0;
+    for (int i = 0; i < match->n; i++) {
+        pmAstromMatch *pair = match->data[i];
+        pmAstromObj *raw = rawstars->data[pair->raw];
+        pmAstromObj *ref = refstars->data[pair->ref];
+
+        if (!isfinite(raw->Mag)) continue;
+        if (raw->Mag < iMagMin) continue;
+        if (raw->Mag > iMagMax) continue;
+        if (ref->Mag < rMagMin) continue;
+        if (ref->Mag > rMagMax) continue;
+
+        xVec->data.F32[n] = raw->chip->y;
+        yVec->data.F32[n] = raw->chip->y - ref->chip->y;
+        zVec->data.F32[n] = raw->Mag;
+        n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa, &graphdata, xVec, yVec, zVec, false);
+
+    // *** X vs Y plot (different window)
+    int kapa2 = KapaOpenNamedSocket ("kapa", "XvsY");
+    if (kapa2 == -1) {
+        psError(PS_ERR_UNKNOWN, true, "failure to open kapa");
+        return false;
+    }
+
+    KapaResize (kapa2, 1000, 1000);
+    KapaInitGraph (&graphdata);
+    KapaClearPlots (kapa2);
+
+    graphdata.color = KapaColorByName ("black");
+    graphdata.ptype = 2;
+    graphdata.style = 2;
+
+    psFree (xVec);
+    psFree (yVec);
+    psFree (zVec);
+
+    xVec = psVectorAlloc (rawstars->n, PS_TYPE_F32);
+    yVec = psVectorAlloc (rawstars->n, PS_TYPE_F32);
+    zVec = psVectorAlloc (rawstars->n, PS_TYPE_F32);
+
+    // X vs Y by mag (raw)
+    n = 0;
+    for (int i = 0; i < rawstars->n; i++) {
+        pmAstromObj *raw = rawstars->data[i];
+        if (!isfinite(raw->Mag)) continue;
+        if (raw->Mag < iMagMin) continue;
+        if (raw->Mag > iMagMax) continue;
+
+        xVec->data.F32[n] = raw->chip->x;
+        yVec->data.F32[n] = raw->chip->y;
+        zVec->data.F32[n] = raw->Mag;
+        n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimits_OpenGraph (kapa2, &graphdata, xVec, yVec, zVec, false);
+
+    graphdata.color = KapaColorByName ("red");
+    graphdata.ptype = 7;
+    graphdata.style = 2;
+
+    psFree (xVec);
+    psFree (yVec);
+    psFree (zVec);
+
+    xVec = psVectorAlloc (refstars->n, PS_TYPE_F32);
+    yVec = psVectorAlloc (refstars->n, PS_TYPE_F32);
+    zVec = psVectorAlloc (refstars->n, PS_TYPE_F32);
+
+    // X vs Y by mag (raw)
+    n = 0;
+    for (int i = 0; i < refstars->n; i++) {
+        pmAstromObj *ref = refstars->data[i];
+        if (!isfinite(ref->Mag)) continue;
+        if (ref->Mag < rMagMin) continue;
+        if (ref->Mag > rMagMax) continue;
+
+        xVec->data.F32[n] = ref->chip->x;
+        yVec->data.F32[n] = ref->chip->y;
+        zVec->data.F32[n] = ref->Mag;
+        n++;
+    }
+    xVec->n = yVec->n = zVec->n = n;
+    pmKapaPlotVectorTriple_AutoLimitsZscale_OpenGraph (kapa2, &graphdata, xVec, yVec, zVec, false);
+
+    // pause and wait for user input:
+    // continue, save (provide name), ??
+    char key[10], name[80];
+    fprintf (stdout, "(s)ave plot or [c]ontinue? ");
+    if (!fgets (key, 8, stdin)) {
+        psWarning("Couldn't read anything");
+    } else if (key[0] == 's') {
+        fprintf (stdout, "enter plot name [chipfit.png]: ");
+        if (fscanf (stdin, "%s", name) != 1) {
+            psWarning("Couldn't read name");
+        } else if (!strcmp (name, "")) {
+            strcpy (name, "chipfit.png");
+        }
+        KapaPNG (kapa, name);
+    }
+
+    close (kapa2);
+    psFree (xVec);
+    psFree (yVec);
+    psFree (zVec);
+    return true;
+}
+
+bool pmKapaPlotVectorTriple_AutoLimitsZscale_OpenGraph (int kapa, Graphdata *graphdata, psVector *xVec, psVector *yVec, psVector *zVec, bool increasing)
+{
+
+    // set limits based on data values
+    float zmin = +FLT_MAX;
+    float zmax = -FLT_MAX;
+    for (int i = 0; i < xVec->n; i++) {
+        zmin = PS_MIN (zmin, zVec->data.F32[i]);
+        zmax = PS_MAX (zmax, zVec->data.F32[i]);
+    }
+
+    // set the scale vector
+    psVector *zScale = psVectorAlloc (zVec->n, PS_DATA_F32);
+
+    float range = zmax - zmin;
+    if (range == 0.0) {
+        psVectorInit (zScale, 1.0);
+    } else {
+        for (int i = 0; i < zVec->n; i++) {
+            if (increasing) {
+                zScale->data.F32[i] = PS_MIN (1.5, PS_MAX(0.05, 1.5*(zVec->data.F32[i] - zmin)/range));
+            } else {
+                zScale->data.F32[i] = PS_MIN (1.5, PS_MAX(0.05, 1.5*(zmax - zVec->data.F32[i])/range));
+            }
+        }
+    }
+
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, graphdata);
+
+    // the point size will be scaled from the z vector
+    graphdata->size = -1;
+    KapaPrepPlot (kapa, xVec->n, graphdata);
+    KapaPlotVector (kapa, xVec->n, xVec->data.F32, "x");
+    KapaPlotVector (kapa, yVec->n, yVec->data.F32, "y");
+    KapaPlotVector (kapa, zVec->n, zScale->data.F32, "z");
+    psFree (zScale);
+    return true;
+}
+
+# else
+
+bool psastroPlotRawstars (psArray *rawstars, pmFPA *fpa, pmChip *chip, psMetadata *recipe)
+{
+    return false;
+}
+
+bool psastroPlotRefstars (psArray *refstars, psMetadata *recipe)
+{
+    return false;
+}
+
+bool psastroPlotOneChipFit (psArray *rawstars, psArray *refstars, psArray *match,
+    psMetadata *recipe)
+{
+    return false;
+}
+
+# endif
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroErrorCodes.c.in
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroErrorCodes.c.in	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroErrorCodes.c.in	(revision 23321)
@@ -0,0 +1,37 @@
+/** @file psastroErrorCodes.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "pslib.h"
+#include "psastroErrorCodes.h"
+
+/*
+ * The line
+    { PSASTRO_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+void psastroErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { PSASTRO_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { PSASTRO_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = PSASTRO_ERR_NERROR - PSASTRO_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroErrorCodes.dat
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroErrorCodes.dat	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroErrorCodes.dat	(revision 23321)
@@ -0,0 +1,12 @@
+#
+# This file is used to generate pmErrorClasses.h
+#
+BASE = 300		First value we use; lower values belong to psLib
+UNKNOWN			Unknown PM error code
+NOT_IMPLEMENTED		Desired feature is not yet implemented
+ARGUMENTS		Incorrect arguments
+CONFIG			Problem in configure files
+IO			Problem in FITS I/O
+WCS      		Error interpreting FITS WCS information
+DATA                    Problem in data values
+REFSTARS                Problem in data values
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroErrorCodes.h.in
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroErrorCodes.h.in	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroErrorCodes.h.in	(revision 23321)
@@ -0,0 +1,30 @@
+/** @file psastroErrorCodes.h
+ *
+ *  @brief
+ *
+ *  @ingroup psastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#if !defined(PSASTRO_ERROR_CODES_H)
+#define PSASTRO_ERROR_CODES_H
+/*
+ * The line
+ *  PSASTRO_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    PSASTRO_ERR_BASE = 600,
+    PSASTRO_ERR_${ErrorCode},
+    PSASTRO_ERR_NERROR
+} psastroErrorCode;
+
+void psastroErrorRegister(void);
+
+#endif
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroFixChips.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroFixChips.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroFixChips.c	(revision 23321)
@@ -0,0 +1,339 @@
+/** @file psastroFixChips.c
+ *
+ *  @brief
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.9 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-09 21:25:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+# define NONLIN_TOL 0.001 ///< tolerance in pixels
+# define DEBUG 0
+
+// XXX I think the 'badAstrom' tests may need to be adjusted: see eg the nominal rotation of
+// the chips
+bool psastroFixChips (pmConfig *config, psMetadata *recipe) {
+
+    bool status;
+    FILE *f;
+    char *chipName;
+
+    bool fixChips = psMetadataLookupBool (&status, config->arguments, "PSASTRO.FIX.CHIPS");
+    if (!status) {
+        fixChips = psMetadataLookupBool (&status, recipe, "PSASTRO.FIX.CHIPS");
+    }
+    if (!fixChips) return true;
+
+    // identify reference astrometry table
+    // if not defined, correction was not requested; skip step
+    pmFPAfile *astrom = psMetadataLookupPtr (NULL, config->files, "PSASTRO.MODEL");
+    if (!astrom) return true;
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find input data");
+        return false;
+    }
+
+    // acceptable limits
+    double pixelTol = psMetadataLookupF32 (&status, recipe, "PSASTRO.PIXEL.TOLERANCE");
+    if (!status) psAbort ("missing recipe value");
+
+    double angleTol = psMetadataLookupF32 (&status, recipe, "PSASTRO.ANGLE.TOLERANCE");
+    if (!status) psAbort ("missing recipe value");
+
+    // make sure the astrometry model is loaded.  de-activate all files except PSASTRO.MODEL.
+    // supply the input concepts so the model is defined using the RA, DEC, POSANGLE of the input
+    // image.
+    psFree (astrom->fpa->concepts);
+    astrom->fpa->concepts = psMemIncrRefCounter (input->fpa->concepts);
+    pmFPAfileActivate (config->files, false, NULL);
+    pmFPAfileActivate (config->files, true, "PSASTRO.MODEL");
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+        psError (PS_ERR_IO, false, "Can't load the astrometry model file");
+        return false;
+    }
+
+    // We now have a set of chip solutions and a set of predictions.  Measure the overall offset and
+    // rotation of the two systems by comparing the chip corners, projected onto the focal plane
+    // (all 4 to prevent solutions tied to a single corner)
+
+    psVector *xObs = psVectorAllocEmpty (4*input->fpa->chips->n, PS_TYPE_F32);
+    psVector *yObs = psVectorAllocEmpty (4*input->fpa->chips->n, PS_TYPE_F32);
+    psVector *xRef = psVectorAllocEmpty (4*input->fpa->chips->n, PS_TYPE_F32);
+    psVector *yRef = psVectorAllocEmpty (4*input->fpa->chips->n, PS_TYPE_F32);
+
+    int nPts = 0;
+
+    if (DEBUG) {
+        f = fopen ("corners.raw.dat", "w");
+        chipName = NULL;
+    }
+
+    pmChip *obsChip = NULL;
+    while ((obsChip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+        if (!obsChip->process || !obsChip->file_exists || !obsChip->data_exists) { continue; }
+
+        // XXX we are currently inconsistent with marking the good vs the bad data
+        // psastroChipAstrom sets data_exists to false if the fit is bad.  this is
+        // probably wrong since it implies there is no data!
+
+        // skip chips for which the astrometry failed (NASTRO == 0)
+        if (!obsChip->cells->n) continue;
+        pmCell *cell = obsChip->cells->data[0];
+        if (!cell) continue;
+
+        if (!cell->readouts->n) continue;
+        pmReadout *readout = cell->readouts->data[0];
+        if (!readout) continue;
+
+        psMetadata *updates = psMetadataLookupMetadata (&status, readout->analysis, "PSASTRO.HEADER");
+        if (!updates) continue;
+
+        int nAstro = psMetadataLookupS32 (&status, updates, "NASTRO");
+        if (!nAstro) continue;
+
+        // set the chip astrometry using the astrom file
+        pmChip *refChip = pmFPAviewThisChip (view, astrom->fpa);
+
+        psRegion *region = pmChipPixels (obsChip);
+        psPlane ptCP, ptFP;
+
+        ptCP.x = region->x0; ptCP.y = region->y0;
+        psPlaneTransformApply (&ptFP, obsChip->toFPA, &ptCP);
+        xObs->data.F32[nPts] = ptFP.x;
+        yObs->data.F32[nPts] = ptFP.y;
+        psPlaneTransformApply (&ptFP, refChip->toFPA, &ptCP);
+        xRef->data.F32[nPts] = ptFP.x;
+        yRef->data.F32[nPts] = ptFP.y;
+
+        if (DEBUG) {
+            chipName = psMetadataLookupStr(NULL, obsChip->concepts, "CHIP.NAME");
+            fprintf (f, "%s  %f %f  %f %f\n", chipName, xObs->data.F32[nPts], yObs->data.F32[nPts], xRef->data.F32[nPts], yRef->data.F32[nPts]);
+        }
+        nPts ++;
+
+        ptCP.x = region->x0; ptCP.y = region->y1;
+        psPlaneTransformApply (&ptFP, obsChip->toFPA, &ptCP);
+        xObs->data.F32[nPts] = ptFP.x;
+        yObs->data.F32[nPts] = ptFP.y;
+        psPlaneTransformApply (&ptFP, refChip->toFPA, &ptCP);
+        xRef->data.F32[nPts] = ptFP.x;
+        yRef->data.F32[nPts] = ptFP.y;
+
+        if (DEBUG) {
+            chipName = psMetadataLookupStr(NULL, obsChip->concepts, "CHIP.NAME");
+            fprintf (f, "%s  %f %f  %f %f\n", chipName, xObs->data.F32[nPts], yObs->data.F32[nPts], xRef->data.F32[nPts], yRef->data.F32[nPts]);
+        }
+        nPts ++;
+
+        ptCP.x = region->x1; ptCP.y = region->y1;
+        psPlaneTransformApply (&ptFP, obsChip->toFPA, &ptCP);
+        xObs->data.F32[nPts] = ptFP.x;
+        yObs->data.F32[nPts] = ptFP.y;
+        psPlaneTransformApply (&ptFP, refChip->toFPA, &ptCP);
+        xRef->data.F32[nPts] = ptFP.x;
+        yRef->data.F32[nPts] = ptFP.y;
+
+        if (DEBUG) {
+            chipName = psMetadataLookupStr(NULL, obsChip->concepts, "CHIP.NAME");
+            fprintf (f, "%s  %f %f  %f %f\n", chipName, xObs->data.F32[nPts], yObs->data.F32[nPts], xRef->data.F32[nPts], yRef->data.F32[nPts]);
+        }
+        nPts ++;
+
+        ptCP.x = region->x1; ptCP.y = region->y0;
+        psPlaneTransformApply (&ptFP, obsChip->toFPA, &ptCP);
+        xObs->data.F32[nPts] = ptFP.x;
+        yObs->data.F32[nPts] = ptFP.y;
+        psPlaneTransformApply (&ptFP, refChip->toFPA, &ptCP);
+        xRef->data.F32[nPts] = ptFP.x;
+        yRef->data.F32[nPts] = ptFP.y;
+
+        if (DEBUG) {
+            chipName = psMetadataLookupStr(NULL, obsChip->concepts, "CHIP.NAME");
+            fprintf (f, "%s  %f %f  %f %f\n", chipName, xObs->data.F32[nPts], yObs->data.F32[nPts], xRef->data.F32[nPts], yRef->data.F32[nPts]);
+        }
+        nPts ++;
+
+        psFree (region);
+    }
+    xObs->n = yObs->n = xRef->n = yRef->n = nPts;
+    if (DEBUG) fclose (f);
+
+    psPlaneTransform *map = psPlaneTransformAlloc (1, 1);
+
+    psVector *mask = psVectorAlloc (nPts, PS_TYPE_VECTOR_MASK);
+    psVectorInit (mask, 0);
+
+    psStats *stats = psStatsAlloc (PS_STAT_CLIPPED_MEAN | PS_STAT_CLIPPED_STDEV);
+    stats->clipIter = 1;
+
+    for (int i = 0; i < 3; i++) {
+        psVectorClipFitPolynomial2D (map->x, stats, mask, 0xff, xObs, NULL, xRef, yRef);
+        psTrace ("psModules.astrom", 3, "x resid: %f +/- %f (%ld of %ld)\n", stats->clippedMean, stats->clippedStdev, stats->clippedNvalues, xObs->n);
+
+        psVectorClipFitPolynomial2D (map->y, stats, mask, 0xff, yObs, NULL, xRef, yRef);
+        psTrace ("psModules.astrom", 3, "y resid: %f +/- %f (%ld of %ld)\n", stats->clippedMean, stats->clippedStdev, stats->clippedNvalues, yObs->n);
+    }
+
+    // loop over all chips, select the outliers, and replace the measured astrometry with the model
+    // the measured transformation above must be applied to make the comparison, and also then applied to the
+    // model transformation
+
+    if (DEBUG) {
+        f = fopen ("corners.fit.dat", "w");
+        for (int i = 0; i < xObs->n; i++) {
+            psPlane obsCoord, refCoord;
+            refCoord.x = xRef->data.F32[i];
+            refCoord.y = yRef->data.F32[i];
+            psPlaneTransformApply (&obsCoord, map, &refCoord);
+            fprintf (f, "%f %f  %f %f  %f %f\n", xObs->data.F32[i], yObs->data.F32[i], xRef->data.F32[i], yRef->data.F32[i], obsCoord.x, obsCoord.y);
+        }
+        fclose (f);
+    }
+
+    psFree (xRef);
+    psFree (yRef);
+    psFree (mask);
+    psFree (stats);
+
+    psFree (view);
+    view = pmFPAviewAlloc (0);
+
+    while ((obsChip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, obsChip->file_exists, obsChip->process);
+        if (!obsChip->process || !obsChip->file_exists || !obsChip->data_exists) { continue; }
+
+        // set the chip astrometry using the astrom file
+        pmChip *refChip = pmFPAviewThisChip (view, astrom->fpa);
+
+        // bad Astrometry test:  ref pixel or angle outside nominal
+
+        psPlane refPixel = {0.0, 0.0, 0.0, 0.0};
+        psPlane obsCoord, refCoord, tmpCoord;
+
+        // find location of 0,0 pixel in focal plane coords for this chip
+        psPlaneTransformApply (&obsCoord, obsChip->toFPA, &refPixel);
+
+        // find location of 0,0 pixel in focal plane coords for ref chip
+        // apply the global field rotation and offset before comparing
+        psPlaneTransformApply (&tmpCoord, refChip->toFPA, &refPixel);
+        psPlaneTransformApply (&refCoord, map, &tmpCoord);
+
+        psPlane offPixel = {100.0, 0.0, 100.0, 0.0};
+        psPlane obsOffPt, refOffPt;
+
+        // find location of 0,0 pixel in focal plane coords for this chip
+        psPlaneTransformApply (&obsOffPt, obsChip->toFPA, &offPixel);
+
+        // find location of 0,0 pixel in focal plane coords for ref chip
+        psPlaneTransformApply (&tmpCoord, refChip->toFPA, &offPixel);
+        psPlaneTransformApply (&refOffPt, map, &tmpCoord);
+
+        double obsAngle = PM_DEG_RAD*atan2 (obsOffPt.y - obsCoord.y, obsOffPt.x - obsCoord.x);
+        double refAngle = PM_DEG_RAD*atan2 (refOffPt.y - refCoord.y, refOffPt.x - refCoord.x);
+
+        bool badAstrom = false;
+        badAstrom |= fabs(obsCoord.x - refCoord.x) > pixelTol;
+        badAstrom |= fabs(obsCoord.y - refCoord.y) > pixelTol;
+        badAstrom |= fabs(obsAngle   - refAngle)   > angleTol;
+
+        fprintf (stderr, "chip %d, angle: %f, pixel: %f,%f\n", view->chip, obsAngle - refAngle, obsCoord.x - refCoord.x, obsCoord.y - refCoord.y);
+
+        // XXX for now, just use first readout
+        pmCell *cell = obsChip->cells->data[0];
+        pmReadout *readout = cell->readouts->data[0];
+
+        // update the header (pull or create local view to entry on readout->analysis)
+        psMetadata *updates = psMetadataLookupMetadata (&status, readout->analysis, "PSASTRO.HEADER");
+        if (!updates) {
+            updates = psMetadataAlloc ();
+            psMetadataAddMetadata (readout->analysis, PS_LIST_TAIL, "PSASTRO.HEADER",  PS_META_REPLACE, "psastro header stats", updates);
+            psFree (updates);
+        }
+
+        psMetadataAddF32 (updates, PS_LIST_TAIL, "AST_DX", PS_META_REPLACE, "chip x offset wrt model", obsCoord.x - refCoord.x);
+        psMetadataAddF32 (updates, PS_LIST_TAIL, "AST_DY", PS_META_REPLACE, "chip y offset wrt model", obsCoord.y - refCoord.y);
+        psMetadataAddF32 (updates, PS_LIST_TAIL, "AST_DT", PS_META_REPLACE, "chip rot offset wrt model", obsAngle - refAngle);
+
+        // for successful chips, save the measured offsets in the header
+        if (!badAstrom) continue;
+
+        // XXX for now, let's just fail on the bad chips.  In the future, let's try to recover, but we still need to
+        // catch the failures relative to the model
+        psMetadataAddS32 (updates, PS_LIST_TAIL, "NASTRO", PS_META_REPLACE, "number of astrometry stars", 0);
+        continue;
+
+        psLogMsg ("psastro", PS_LOG_INFO, "fixing chip %d, angle: %f, pixel: %f,%f\n",
+                  view->chip, obsAngle - refAngle, obsCoord.x - refCoord.x, obsCoord.y - refCoord.y);
+
+        psFree (obsChip->toFPA);
+        psFree (obsChip->fromFPA);
+
+        // apply the exiting fromTPA transformation to make the new toFPA consistent with the toTPA layter
+        // XXX this only works if toTPA is at most a linear transformation
+        psPlaneTransform *toFPA = psPlaneTransformAlloc(refChip->toFPA->x->nX, refChip->toFPA->x->nY);
+        for (int i = 0; i <= refChip->toFPA->x->nX; i++) {
+            for (int j = 0; j <= refChip->toFPA->x->nY; j++) {
+                double f1 = refChip->toFPA->x->coeffMask[i][j] ? 0.0 : map->x->coeff[1][0]*refChip->toFPA->x->coeff[i][j];
+                double f2 = refChip->toFPA->y->coeffMask[i][j] ? 0.0 : map->x->coeff[0][1]*refChip->toFPA->y->coeff[i][j];
+                toFPA->x->coeff[i][j] = f1 + f2;
+
+                double g1 = refChip->toFPA->x->coeffMask[i][j] ? 0.0 : map->y->coeff[1][0]*refChip->toFPA->x->coeff[i][j];
+                double g2 = refChip->toFPA->y->coeffMask[i][j] ? 0.0 : map->y->coeff[0][1]*refChip->toFPA->y->coeff[i][j];
+                toFPA->y->coeff[i][j] = g1 + g2;
+            }
+        }
+        toFPA->x->coeff[0][0] += map->x->coeff[0][0];
+        toFPA->y->coeff[0][0] += map->y->coeff[0][0];
+
+        psRegion *region = pmChipPixels (obsChip);
+        obsChip->toFPA   = toFPA;
+        obsChip->fromFPA = psPlaneTransformInvert(NULL, obsChip->toFPA, *region, 50);
+        psFree (region);
+
+        // use the new position to re-try the match fit
+        // select the raw objects for this readout
+        psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+        if (rawstars == NULL) { continue; }
+
+        // select the raw objects for this readout
+        psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+        if (refstars == NULL) { continue; }
+
+        // the absolute minimum number of stars is 4 (for order = 1)
+        if ((rawstars->n < 4) || (refstars->n < 4)) {
+            readout->data_exists = false;
+            psLogMsg ("psastro", 3, "insufficient rawstars (%ld) or refstars (%ld)",
+                      rawstars->n, refstars->n);
+            continue;
+        }
+
+        psastroUpdateChipToFPA (input->fpa, obsChip, rawstars, refstars);
+
+        // XXX update the header with info to reflect the failure
+        if (!psastroOneChipFit (input->fpa, obsChip, refstars, rawstars, recipe, updates)) {
+            readout->data_exists = false;
+            psLogMsg ("psastro", 3, "failed to find a solution\n");
+            continue;
+        }
+
+        pmAstromWriteWCS (updates, input->fpa, obsChip, NONLIN_TOL);
+    }
+
+    pmAstromVisualPlotFixChips (input, xObs, yObs);
+    psFree (xObs);
+    psFree (yObs);
+    psFree (map);
+    psFree (view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroFixChipsTest.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroFixChipsTest.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroFixChipsTest.c	(revision 23321)
@@ -0,0 +1,261 @@
+/** @file psastroFixChipsTest.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+# define NONLIN_TOL 0.001 /* tolerance in pixels */
+
+bool psPlaneTransformPrint (FILE *f, psPlaneTransform *in) {
+
+  fprintf (f, "x (%d,%d):\n", in->x->nX, in->x->nY);
+  for (int i = 0; i <= in->x->nX; i++) {
+    fprintf (f, " %d : ", i);
+    for (int j = 0; j <= in->x->nY; j++) {
+      fprintf (f, "%f ", in->x->coeff[i][j]);
+    }
+    fprintf (f, "\n");
+  }
+  fprintf (f, "y (%d,%d):\n", in->y->nX, in->y->nY);
+  for (int i = 0; i <= in->y->nX; i++) {
+    fprintf (f, " %d : ", i);
+    for (int j = 0; j <= in->y->nY; j++) {
+      fprintf (f, "%f ", in->y->coeff[i][j]);
+    }
+    fprintf (f, "\n");
+  }
+  return TRUE;
+}
+
+// XXX the designed version is not working correctly. Here we load a model which just has
+// the CRPIX1, CRPIX2 values from another image. Compare those positions to the current
+// postions, make an average (DC) correction, then fix the outliers.
+bool psastroFixChipsTest (pmConfig *config, psMetadata *recipe) {
+
+    bool status;
+
+    bool fixChips = psMetadataLookupBool (&status, config->arguments, "PSASTRO.FIX.CHIPS");
+    if (!status) {
+	fixChips = psMetadataLookupBool (&status, recipe, "PSASTRO.FIX.CHIPS");
+    }
+    if (!fixChips) return true;
+
+    // identify reference astrometry table
+    // if not defined, correction was not requested; skip step
+    char *modelFile = psMetadataLookupStr (&status, recipe, "PSASTRO.ROUGH.MODEL");
+    if (!modelFile) return true;
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find input data");
+	return false;
+    }
+
+    // physical pixel scale in microns per pixel
+    double pixelScale = psMetadataLookupF32 (&status, recipe, "PSASTRO.PIXEL.SCALE");
+    if (!status) {
+	psError(PS_ERR_IO, true, "Failed to lookup pixel scale"); 
+	return false; 
+    } 
+
+    // acceptable limits
+    double pixelTol = psMetadataLookupF32 (&status, recipe, "PSASTRO.PIXEL.TOLERANCE");
+    if (!status) psAbort ("missing recipe value");
+
+    FILE *f = fopen (modelFile, "r");
+    if (f == NULL) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find model data %s", modelFile);
+	return false;
+    }
+
+    float Xo, Yo;
+
+    psArray *refPos = psArrayAllocEmpty (100);
+    while (fscanf (f, "%*s %*d %*d %*f %f %f", &Xo, &Yo) != EOF) {
+	psPlane *pos = psPlaneAlloc();
+	pos->x = Xo;
+	pos->y = Yo;
+	psArrayAdd (refPos, 100, pos);
+	psFree (pos);
+    }
+    assert (refPos->n == 60);
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // loop over all chips, get the current CRPIX1,CRPIX2 values
+    pmChip *obsChip = NULL;
+    psArray *obsPos = psArrayAllocEmpty (100);
+    while ((obsChip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+	psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, obsChip->file_exists, obsChip->process);
+	if (!obsChip->process || !obsChip->file_exists || !obsChip->data_exists) { continue; }
+
+	// extract CRPIX1, CRPIX2:
+	// XXX for now, pull from the header:
+	// pmCell *cell = obsChip->cells->data[0];
+	// pmReadout *readout = cell->readouts->data[0];
+	// psMetadata *header = psMetadataLookupMetadata (&status, readout->analysis, "PSASTRO.HEADER");
+	// Xo = psMetadataLookupF32 (&status, header, "CRPIX1");
+	// Yo = psMetadataLookupF32 (&status, header, "CRPIX2");
+
+	pmAstromWCS *wcs = pmAstromWCSfromFPA(input->fpa, obsChip, NONLIN_TOL);
+	Xo = wcs->crpix1;
+	Yo = wcs->crpix2;
+	psFree (wcs);
+
+	psPlane *pos = psPlaneAlloc();
+	pos->x = Xo;
+	pos->y = Yo;
+	psArrayAdd (obsPos, 100, pos);
+	psFree (pos);
+    }
+    psFree (view);
+    assert (obsPos->n == 60);
+
+    // get the offsets
+    psVector *dX = psVectorAlloc (obsPos->n, PS_TYPE_F32);
+    psVector *dY = psVectorAlloc (obsPos->n, PS_TYPE_F32);
+    for (int i = 0; i < obsPos->n; i++) {
+	psPlane *obs = obsPos->data[i];
+	psPlane *ref = refPos->data[i];
+    
+	if (i < 30) {
+	    dX->data.F32[i] = obs->x - ref->x;
+	    dY->data.F32[i] = obs->y - ref->y;
+	} else {
+	    dX->data.F32[i] = ref->x - obs->x;
+	    dY->data.F32[i] = ref->y - obs->y;
+	}
+	fprintf (stderr, "chip %d : %f - %f : %f, %f - %f: %f\n", i, obs->x, ref->x, dX->data.F32[i], obs->y, ref->y, dY->data.F32[i]);
+    }
+    
+    // get the average offset
+    psStats *stats;
+    stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    psVectorStats (stats, dX, NULL, NULL, 0);
+    Xo = stats->robustMedian;
+    fprintf (stderr, "offset x: %f +/- %f\n", stats->robustMedian, stats->robustStdev);
+    psFree (stats);
+
+    stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    psVectorStats (stats, dY, NULL, NULL, 0);
+    Yo = stats->robustMedian;
+    fprintf (stderr, "offset y: %f +/- %f\n", stats->robustMedian, stats->robustStdev);
+    psFree (stats);
+
+    // measure the rotation of the good chips
+    psVector *dT = psVectorAllocEmpty (obsPos->n, PS_TYPE_F32);
+    for (int i = 0; i < dX->n; i++) {
+	bool badAstrom = false;
+	badAstrom |= (fabs(dX->data.F32[i] - Xo) > pixelTol);
+	badAstrom |= (fabs(dY->data.F32[i] - Yo) > pixelTol);
+	if (badAstrom) continue;
+
+	pmChip *chip = input->fpa->chips->data[i];
+	float theta = atan2 (chip->toFPA->x->coeff[0][1], chip->toFPA->x->coeff[1][0]);
+	if (i >= 30) {
+	  theta -= M_PI;
+	}
+	dT->data.F32[dT->n] = theta;
+	dT->n ++;
+    }
+    stats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+    psVectorStats (stats, dT, NULL, NULL, 0);
+    float To = stats->robustMedian;
+    fprintf (stderr, "offset t: %f +/- %f\n", stats->robustMedian, stats->robustStdev);
+    psFree (stats);
+
+    // find the discrepant chips and reset
+    for (int i = 0; i < dX->n; i++) {
+	bool badAstrom = false;
+	badAstrom |= (fabs(dX->data.F32[i] - Xo) > pixelTol);
+	badAstrom |= (fabs(dY->data.F32[i] - Yo) > pixelTol);
+	if (!badAstrom) continue;
+
+	fprintf (stderr, "fixing chip %d, offset: %f %f\n",
+		 i, dX->data.F32[i] - Xo, dY->data.F32[i] - Yo);
+
+	// XXX this stuff below should be applied to each readout...
+	// XXX for now, just use first readout
+	pmFPA  *fpa  = input->fpa;
+	pmChip *chip = fpa->chips->data[i];
+	pmCell *cell = chip->cells->data[0];
+	pmReadout *readout = cell->readouts->data[0];
+
+	pmAstromWCS *wcs = pmAstromWCSfromFPA(fpa, chip, NONLIN_TOL);
+
+	psPlane *ref = refPos->data[i];
+	wcs->crpix1 = ref->x + Xo;
+	wcs->crpix2 = ref->y + Yo;
+
+	pmAstromWCStoFPA (fpa, chip, wcs, pixelScale);
+	psFree (wcs);
+
+	// apply average rotation
+	psPlaneTransform *toFPA = psPlaneTransformRotate (NULL, chip->toFPA, To);
+	psFree (chip->toFPA);
+	chip->toFPA = toFPA;
+
+	psPlaneTransform *fromFPA = psPlaneTransformRotate (NULL, chip->fromFPA, -To);
+	psFree (chip->fromFPA);
+	chip->fromFPA = fromFPA;
+
+	// XXX did we get the offset right?
+	wcs = pmAstromWCSfromFPA(fpa, chip, NONLIN_TOL);
+	psFree (wcs);
+
+	// save WCS and analysis metadata in update header
+	// (pull or create local view to entry on readout->analysis)
+	psMetadata *updates = psMetadataLookupMetadata (&status, readout->analysis, "PSASTRO.HEADER");
+	if (!updates) {
+	    updates = psMetadataAlloc ();
+	    psMetadataAddMetadata (readout->analysis, PS_LIST_TAIL, "PSASTRO.HEADER",  PS_META_REPLACE, "psastro header stats", updates);
+	    psFree (updates);
+	}
+
+	// select the raw objects for this readout
+	psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+	if (rawstars == NULL) { continue; }
+
+	// select the raw objects for this readout
+	psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+	if (refstars == NULL) { continue; }
+
+	// the absolute minimum number of stars is 4 (for order = 1)
+	if ((rawstars->n < 4) || (refstars->n < 4)) {
+	    readout->data_exists = false;
+	    psLogMsg ("psastro", 3, "insufficient rawstars (%ld) or refstars (%ld)", 
+		      rawstars->n, refstars->n);
+	    continue;
+	} 
+
+	psastroUpdateChipToFPA (fpa, chip, rawstars, refstars);
+
+	// XXX skip the re-fit step for a test
+	continue;
+
+	// XXX update the header with info to reflect the failure
+	if (!psastroOneChipFit (fpa, chip, refstars, rawstars, recipe, updates)) {
+	    readout->data_exists = false;
+	    psLogMsg ("psastro", 3, "failed to find a solution\n");
+	    continue;
+	}
+
+	pmAstromWriteWCS (updates, fpa, chip, NONLIN_TOL);
+    }
+
+    psFree (dT);
+    psFree (dX);
+    psFree (dY);
+    psFree (refPos);
+    psFree (obsPos);
+  
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroInternal.h
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroInternal.h	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroInternal.h	(revision 23321)
@@ -0,0 +1,29 @@
+/** @file psastroInternal.h
+ *
+ *  @brief
+ *
+ *  @ingroup psastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# ifdef HAVE_CONFIG_H
+# include <config.h>
+# endif
+
+# ifndef PSASTRO_INTERNAL_H
+# define PSASTRO_INTERNAL_H
+
+# include <stdio.h>
+# include <string.h>
+# include <strings.h>  // for strcasecmp
+# include <unistd.h>   // for unlink
+# include <pslib.h>
+# include <psmodules.h>
+# include <ppStats.h>
+# include "psastro.h"
+
+#endif
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroLoadRefstars.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroLoadRefstars.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroLoadRefstars.c	(revision 23321)
@@ -0,0 +1,324 @@
+/** @file psastroLoadRefstars.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.36 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-09 21:25:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+# define ELIXIR_MODE 1
+
+char *psastroSetMagLimit (float *minMag, float *maxRho, pmConfig *config);
+
+psArray *psastroLoadRefstars (pmConfig *config) {
+
+    int fd;
+    bool status;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+
+    // DVO APIs expect decimal degrees
+    float RAmin  = DEG_RAD*psMetadataLookupF32(NULL, recipe, "RA_MIN");
+    float RAmax  = DEG_RAD*psMetadataLookupF32(NULL, recipe, "RA_MAX");
+    float DECmin = DEG_RAD*psMetadataLookupF32(NULL, recipe, "DEC_MIN");
+    float DECmax = DEG_RAD*psMetadataLookupF32(NULL, recipe, "DEC_MAX");
+
+    // extra field fraction to add
+    double fieldPadding = psMetadataLookupF32 (&status, recipe, "PSASTRO.FIELD.PADDING");
+    PS_ASSERT (status, NULL);
+
+    float dRA = RAmax - RAmin;
+    RAmin -= dRA * fieldPadding;
+    RAmax += dRA * fieldPadding;
+
+    float dDEC = DECmax - DECmin;
+    DECmin -= dDEC * fieldPadding;
+    DECmax += dDEC * fieldPadding;
+
+    // grab the PSASTRO.CATDIR name from the PSASTRO recipe
+    char *catdir_recipe = psMetadataLookupStr(&status, recipe, "PSASTRO.CATDIR");
+    psAssert (catdir_recipe, "Need a recipe for the catdir!");
+
+    // substitute abstract name with concrete name, if present in PSASTRO.CATDIRS
+    psMetadata *catdirs = psMetadataLookupMetadata(&status, config->site, "PSASTRO.CATDIRS"); // List of cameras
+    if (!catdirs) {
+        psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find PSASTRO.CATDIRS in the system configuration.\n");
+        return false;
+    }
+    char *catdir_virtual = psMetadataLookupStr(&status, catdirs, catdir_recipe);
+    char *catdir = (catdir_virtual == NULL) ? catdir_recipe : catdir_virtual;
+
+    // convert the uri to a real filename (ie, path://foobar)
+    psString CATDIR = pmConfigConvertFilename(catdir, config, false, false); // Resolved filename
+    PS_ASSERT (CATDIR, NULL);
+
+    char *getstarCommand = psStringCopy(psMetadataLookupStr(NULL, recipe, "DVO.GETSTAR"));
+    PS_ASSERT (getstarCommand, NULL);
+
+    char *outformat = psMetadataLookupStr(NULL, recipe, "DVO.GETSTAR.OUTFORMAT");
+    PS_ASSERT (outformat, NULL);
+
+    // we have an exposure with a certain filter and exposure time: choose the maximum
+    // reference star density for stars fainter than an equivalent instrumental mag limit
+    float minMag;
+    float maxRho;
+    char *photcode = psastroSetMagLimit (&minMag, &maxRho, config);
+    PS_ASSERT (photcode, NULL);
+
+    // issue the following command:
+    // getstar -region RAmin RAmax DECmin DECmax
+    char tempFile[64];
+    sprintf (tempFile, "/tmp/psastro.XXXXXX");
+    if ((fd = mkstemp (tempFile)) == -1) {
+        psError(PSASTRO_ERR_REFSTARS, true, "error creating temp output file\n");
+        psFree(CATDIR);
+        return NULL;
+    }
+    close (fd);
+
+    psTimerStart ("psastro");
+
+    // supply a known output format (for CATALOG output) so the code below knows what to read
+    if (ELIXIR_MODE) {
+        psStringAppend (&getstarCommand, " -D CATMODE mef -D CATFORMAT elixir");
+    } else {
+        psStringAppend (&getstarCommand, " -D CATMODE mef -D CATFORMAT panstarrs");
+    }
+
+    // check for default name (use .ptolemyrc), or use specified CATDIR
+    if (strcasecmp(CATDIR, "NONE")) {
+        psStringAppend (&getstarCommand, " -D CATDIR %s", CATDIR);
+    }
+    psFree(CATDIR);
+
+    // supply the max magnitude, the output format, and the photcode
+    if (strcasecmp (photcode, "NONE")) {
+        psStringAppend (&getstarCommand, " -photcode %s -minmag %f -max-density %f", photcode, minMag, maxRho);
+    }
+    psStringAppend (&getstarCommand, " -format %s", outformat);
+
+    // add region and output filename
+    psStringAppend (&getstarCommand, " -region %f %f %f %f -o %s", RAmin, DECmin, RAmax, DECmax, tempFile);
+    psTrace ("psastro", 3, "%s\n", getstarCommand);
+
+    // XXX use psPipe: catch stderr, stdout, allow for Nsec timeout...
+    // use fork to add timeout capability
+    status = system (getstarCommand);
+    if (status) {
+        psError(PSASTRO_ERR_REFSTARS, true, "error loading reference data\n");
+        return NULL;
+    }
+    psFree (getstarCommand);
+
+    psLogMsg ("psastro", 3, "ran getstar : %f sec\n", psTimerMark ("psastro"));
+
+    // the output from getstar is a file with the Average table
+    psFits *fits = psFitsOpen (tempFile, "r");
+
+    psTimerStart ("psastro");
+
+    psArray *refstars = NULL;
+    if (!strcmp (outformat, "CATALOG")) {
+      refstars = psastroReadGetstarCatalog (fits);
+    }
+    if (!strcmp (outformat, "PS1_DEV_0")) {
+      refstars = psastroReadGetstar_PS1_DEV_0 (fits);
+    }
+    if (refstars == NULL) {
+        psError(PSASTRO_ERR_REFSTARS, true, "error reading reference data\n");
+        psFitsClose (fits);
+        return NULL;
+    }
+
+    psLogMsg ("psastro", 3, "loaded %ld reference stars : %f sec\n", refstars->n, psTimerMark ("psastro"));
+
+    psTrace ("psastro", 3, "loaded %ld reference stars from (%10.6f,%10.6f) - (%10.6f,%10.6f)\n",
+             refstars->n, RAmin, DECmin, RAmax, DECmax);
+
+    psFitsClose (fits);
+    unlink (tempFile);
+
+    // dump or plot the available refstars
+    if (psTraceGetLevel("psastro.dump") > 0) {
+        psastroDumpRefstars (refstars, "refstars.dat");
+    }
+
+    pmAstromVisualPlotRefStars (refstars, recipe);
+
+    if (psTraceGetLevel("psastro.plot") > 0) {
+        psastroPlotRefstars (refstars, recipe);
+    }
+
+    return refstars;
+}
+
+psArray *psastroReadGetstarCatalog (psFits *fits) {
+
+    bool status;
+
+    if (ELIXIR_MODE) {
+        psFitsMoveExtName (fits, "DVO_AVERAGE_ELIXIR");
+    } else {
+        psFitsMoveExtName (fits, "DVO_AVERAGE_PANSTARRS");
+    }
+
+    long numSources = psFitsTableSize(fits); // Number of sources in table
+
+    // convert the Average table to the pmAstromObj entries
+    psArray *refstars = psArrayAllocEmpty (numSources);
+    for (int i = 0; i < numSources; i++) {
+        pmAstromObj *ref = pmAstromObjAlloc ();
+
+        psMetadata *row = psFitsReadTableRow(fits, i); // Table row
+
+        // DVO tables are stored in degrees
+        if (ELIXIR_MODE) {
+            ref->sky->r   = RAD_DEG*psMetadataLookupF32 (&status, row, "RA");
+            ref->sky->d   = RAD_DEG*psMetadataLookupF32 (&status, row, "DEC");
+            ref->Mag      = 0.001*psMetadataLookupS32 (&status, row, "MAG");  // ELIXIR uses millimags
+        } else {
+            ref->sky->r   = RAD_DEG*psMetadataLookupF64 (&status, row, "RA");
+            ref->sky->d   = RAD_DEG*psMetadataLookupF64 (&status, row, "DEC");
+            ref->Mag      = psMetadataLookupF32 (&status, row, "MAG"); // PANSTARRS uses mags
+        }
+
+        // XXX VERY temporary hack to avoid M31 bulge
+        if ((fabs(ref->sky->r - 0.186438) < 0.002) && (fabs(ref->sky->d - 0.720270) < 0.002)) {
+          psFree (ref);
+          psFree (row);
+          continue;
+        }
+
+        psArrayAdd (refstars, 100, ref);
+        psFree (ref);
+        psFree (row);
+    }
+    return refstars;
+}
+
+psArray *psastroReadGetstar_PS1_DEV_0 (psFits *fits) {
+
+    bool status;
+
+    psFitsMoveExtName (fits, "GETSTAR_PS1_DEV_0");
+
+    long numSources = psFitsTableSize(fits); // Number of sources in table
+
+    // convert the Average table to the pmAstromObj entries
+    psArray *refstars = psArrayAllocEmpty (numSources);
+    for (int i = 0; i < numSources; i++) {
+        pmAstromObj *ref = pmAstromObjAlloc ();
+
+        psMetadata *row = psFitsReadTableRow(fits, i); // Table row
+
+        ref->sky->r   = RAD_DEG*psMetadataLookupF32 (&status, row, "RA");
+        ref->sky->d   = RAD_DEG*psMetadataLookupF32 (&status, row, "DEC");
+        ref->Mag      = psMetadataLookupF32 (&status, row, "MAG");
+
+        // XXX VERY temporary hack to avoid M31 bulge
+        if ((fabs(ref->sky->r - 0.186438) < 0.002) && (fabs(ref->sky->d - 0.720270) < 0.002)) {
+          psFree (ref);
+          psFree (row);
+          continue;
+        }
+
+        psArrayAdd (refstars, 100, ref);
+        psFree (ref);
+        psFree (row);
+    }
+    return refstars;
+}
+
+# define ESCAPE(MSG) { \
+  psLogMsg ("psastro", PS_LOG_INFO, MSG); \
+  goto escape; }
+
+char *psastroSetMagLimit (float *minMag, float *maxRho, pmConfig *config) {
+
+    bool status;
+    char *photcode;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+        psLogMsg ("psastro", PS_LOG_DETAIL, "no supplied reference header data");
+        photcode = psStringCopy ("NONE");
+        return photcode;
+    }
+    assert (input->fpa);
+
+    *maxRho = psMetadataLookupF32(&status, recipe, "DVO.GETSTAR.MAX.RHO");
+    if (!status) {
+        psError(PSASTRO_ERR_CONFIG, false, "DVO.GETSTAR.MAX.RHO missing from recipe");
+        return NULL;
+    }
+
+    // select the filter; default to fixed photcode and mag limit otherwise
+    char *filter = psMetadataLookupStr (&status, input->fpa->concepts, "FPA.FILTERID");
+    if (!status) ESCAPE ("missing FPA.FILTER in concepts");
+
+    float exptime = psMetadataLookupF32 (&status, input->fpa->concepts, "FPA.EXPOSURE");
+    if (!status) ESCAPE ("missing FPA.EXPOSURE in concepts");
+
+    // we need to select the PHOTCODE.DATA folder that matches our filter
+    psMetadataItem *item = psMetadataLookup (recipe, "PHOTCODE.DATA");
+    if (!item) ESCAPE ("PHOTCODE.DATA folders missing");
+    if (item->type != PS_DATA_METADATA_MULTI) ESCAPE ("PHOTCODE.DATA not a multi");
+
+    float minInst = psMetadataLookupF32(&status, recipe, "DVO.GETSTAR.MIN.MAG.INST");
+    if (!status) ESCAPE ("missing DVO.GETSTAR.MIN.MAG.INST");
+
+    // PHOTCODE.DATA is a multi of metadata items
+    psListIterator *iter = psListIteratorAlloc(item->data.list, PS_LIST_HEAD, false);
+
+    psMetadataItem *refItem = NULL;
+    while ((refItem = psListGetAndIncrement (iter))) {
+        if (refItem->type != PS_DATA_METADATA) ESCAPE ("PHOTCODE.DATA entry is not a metadata folder");
+
+        char *refFilter = psMetadataLookupStr (&status, refItem->data.md, "FILTER");
+        if (!status) {
+            psLogMsg ("psastro", PS_LOG_INFO, "a PHOTCODE.DATA recipe folder is missing FILTER");
+            continue;
+        }
+
+        // does this entry match the current filter?
+        if (strcmp (refFilter, filter)) continue;
+
+        psLogMsg ("psastro", PS_LOG_DETAIL, "PHOTCODE.DATA found for filter %s", filter);
+
+        float zeropt = psMetadataLookupF32 (&status, refItem->data.md, "ZEROPT");
+        if (!status) {
+            psLogMsg ("psastro", PS_LOG_INFO, "a PHOTCODE.DATA recipe folder is missing FILTER");
+            continue;
+        }
+        photcode = psMetadataLookupStr (&status, refItem->data.md, "PHOTCODE");
+        if (!status) {
+            psLogMsg ("psastro", PS_LOG_INFO, "a PHOTCODE.DATA recipe folder is missing FILTER");
+            continue;
+        }
+
+        // convert the minInst to a calibrated minimum magnitude
+        *minMag = minInst + 2.5*log10(exptime) + zeropt;
+
+        psFree (iter);
+        return photcode;
+    }
+    psFree (iter);
+
+  escape:
+    photcode = psMetadataLookupStr(NULL, recipe, "DVO.GETSTAR.PHOTCODE");
+    PS_ASSERT (photcode, NULL);
+
+    // give up and use fixed value
+    *minMag = psMetadataLookupF32(NULL, recipe, "DVO.GETSTAR.MIN.MAG");
+    return photcode;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroLuminosityFunction.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroLuminosityFunction.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroLuminosityFunction.c	(revision 23321)
@@ -0,0 +1,155 @@
+/** @file psastroLuminosityFunction.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.19 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-09 21:25:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+# define dMag 0.1
+// XXX put this in config?
+
+static void pmLumFuncFree (pmLumFunc *func) {
+
+    if (func == NULL) return;
+
+    return;
+}
+
+pmLumFunc *pmLumFuncAlloc () {
+
+    pmLumFunc *func = (pmLumFunc *) psAlloc(sizeof(pmLumFunc));
+    psMemSetDeallocator(func, (psFreeFunc) pmLumFuncFree);
+
+    func->mMin = 0.0;
+    func->mMax = 0.0;
+    func->slope = 0.0;
+    func->offset = 0.0;
+
+    func->mPeak = 0.0;
+    func->nPeak = 0;
+    func->sPeak = 0;
+
+    return func;
+}
+
+pmLumFunc *psastroLuminosityFunction (psArray *stars, pmLumFunc *rawFunc) {
+
+    if (stars->n == 0) return NULL;
+
+    // determine the max and min magnitude for the array of stars
+    pmAstromObj *star = stars->data[0];
+    double mMin = star->Mag;
+    double mMax = star->Mag;
+    for (int i = 0; i < stars->n; i++) {
+        star = stars->data[i];
+        mMin = PS_MIN (star->Mag, mMin);
+        mMax = PS_MAX (star->Mag, mMax);
+    }
+
+    int nBin = 1 + (mMax - mMin) / dMag;
+    if (nBin <= 1) {
+        psLogMsg ("psastro", 4, "insufficient valid stars for this readout\n");
+        return NULL;
+    }
+
+    // create a histogram of the magnitudes
+    // bin[0] = mMin : mMin + dMag
+    // bin[i] = mMin + i*dMag : mMin + (i+1)*dMag
+    psVector *nMags = psVectorAlloc (nBin, PS_TYPE_F32);
+    psVectorInit (nMags, 0);
+
+    for (int i = 0; i < stars->n; i++) {
+        star = stars->data[i];
+        if (!isfinite(star->Mag)) continue;
+        int bin = (star->Mag - mMin) / dMag;
+        if (bin < 0) psAbort("bin cannot be negative!");
+        if (bin >= nBin) psAbort("bin cannot be > %d!", nBin);
+        nMags->data.F32[bin] += 1.0;
+    }
+
+    // find the peak and position
+    int iPeak = 0;
+    int nPeak = 0;
+    int sPeak = 0;
+    for (int i = 0; i < nMags->n; i++) {
+        if (nMags->data.F32[i] < nPeak) continue;
+        iPeak = i;
+        nPeak = nMags->data.F32[i];
+        sPeak += nPeak;
+    }
+
+    psVector *lnMag = psVectorAllocEmpty (nBin, PS_TYPE_F32);
+    psVector *Mag = psVectorAllocEmpty (nBin, PS_TYPE_F32);
+
+    // create 2 vectors represnting the dlogN/dlogS line
+    // exclude bins with no stars
+    // exclude points after the peak with N < 0.8*peak
+    int n = 0;
+    for (int i = 0; i < nMags->n; i++) {
+        if (nMags->data.F32[i] < 1) continue;
+        if ((i > iPeak) && (nMags->data.F32[i] < 0.8*nPeak)) continue;
+        lnMag->data.F32[n] = log10 (nMags->data.F32[i]);
+        Mag->data.F32[n] = mMin + (i + 0.5)*dMag;
+        n++;
+    }
+    lnMag->n = n;
+    Mag->n = n;
+    psLogMsg ("psastro", 4, "fitting %d points to luminosity function\n", n);
+
+    psVector *mask = psVectorAlloc (Mag->n, PS_TYPE_VECTOR_MASK);
+    psVectorInit (mask, 0);
+
+    psPolynomial1D *line = psPolynomial1DAlloc (PS_POLYNOMIAL_ORD, 1);
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    stats->clipSigma = 3.0;
+    stats->clipIter = 3;
+
+    if (!psVectorClipFitPolynomial1D(line, stats, mask, 0xff, lnMag, NULL, Mag)) {
+        psLogMsg ("psastro", 4, "Failed to fit the luminosity function\n");
+        return(NULL);
+    }
+
+    // find min and max unmasked magnitudes
+    double mMinValid = NAN;
+    double mMaxValid = NAN;
+    for (int i = 0; i < Mag->n; i++) {
+        if (mask->data.PS_TYPE_VECTOR_MASK_DATA[i]) continue;
+        if (isnan(mMinValid) || (Mag->data.F32[i] < mMinValid)) {
+            mMinValid = Mag->data.F32[i];
+        }
+        if (isnan(mMaxValid) || (Mag->data.F32[i] > mMaxValid)) {
+            mMaxValid = Mag->data.F32[i];
+        }
+    }
+
+    psLogMsg ("psastro", 4, "logN = %f + %f mag\n", line->coeff[0], line->coeff[1]);
+    psLogMsg ("psastro", 4, "logN vs mag residuals: %f +/- %f\n", stats->sampleMedian, stats->sampleStdev);
+
+    pmLumFunc *lumFunc = pmLumFuncAlloc ();
+    lumFunc->mMin = mMinValid;
+    lumFunc->mMax = mMaxValid;
+    lumFunc->offset = line->coeff[0];
+    lumFunc->slope = line->coeff[1];
+    lumFunc->mPeak = mMin + (iPeak + 0.5)*dMag;
+    lumFunc->nPeak = nPeak;
+    lumFunc->sPeak = sPeak;
+
+    pmAstromVisualPlotLuminosityFunction(lnMag, Mag, lumFunc, rawFunc);
+
+    psFree (lnMag);
+    psFree (nMags);
+    psFree (Mag);
+    psFree (mask);
+    psFree (line);
+    psFree (stats);
+
+    return (lumFunc);
+}
+
+
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroLuminosityFunctionPlot.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroLuminosityFunctionPlot.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroLuminosityFunctionPlot.c	(revision 23321)
@@ -0,0 +1,100 @@
+#include "psastroInternal.h"
+
+bool psastroLuminosityFunctionPlot (psVector *lnMag, psVector *Mag,
+                                    pmLumFunc *lumFunc, pmLumFunc *rawFunc) {
+    //Set up graph window
+    Graphdata graphdata;
+    int kapa = pmKapaOpen(true);
+    KapaSection section1 = {"s1", .05, .05, .9, .4};
+    KapaSection section2 = {"s2", .05, .55, .9, .4};
+    KapaSection section;
+    if(rawFunc == NULL)
+        section = section1;
+    else
+        section = section2;
+    int DX = 1000, DY = 800;
+    KapaResize(kapa, DX, DY);
+    if (rawFunc == NULL)
+        KapaClearPlots(kapa);
+    KapaInitGraph(&graphdata);
+
+    //Determine Plot Limits
+    graphdata.xmin = 50;
+    graphdata.ymin = 50;
+    graphdata.xmax = -50;
+    graphdata.ymax = -50;
+
+    for(int i = 0; i < lnMag->n; i++) {
+        graphdata.xmin = PS_MIN(graphdata.xmin,   Mag->data.F32[i]);
+        graphdata.ymin = PS_MIN(graphdata.ymin, lnMag->data.F32[i]);
+        graphdata.xmax = PS_MAX(graphdata.xmax,   Mag->data.F32[i]);
+        graphdata.ymax = PS_MAX(graphdata.ymax, lnMag->data.F32[i]);
+    }
+
+    float range = graphdata.xmax - graphdata.xmin;
+    graphdata.xmin -= .05 * range;
+    graphdata.xmax += .05 * range;
+
+    range = graphdata.ymax - graphdata.ymin;
+    graphdata.ymin -= .05 * range;
+    graphdata.ymax += .05 * range;
+
+    //Make a line for the fit
+    float x[2] = {graphdata.xmin, graphdata.xmax};
+    float y[2] = {lumFunc->offset + x[0] * lumFunc->slope,
+                 lumFunc->offset + x[1] * lumFunc->slope};
+
+    //Plot Data
+    KapaSetSection(kapa, &section);
+    KapaSetLimits(kapa, &graphdata);
+
+    KapaSetFont (kapa, "helvetica", 14);
+    KapaBox (kapa, &graphdata);
+    KapaSendLabel (kapa, "Magnitude", KAPA_LABEL_XM);
+    KapaSendLabel (kapa, "Log(N)", KAPA_LABEL_YM);
+    if (rawFunc == NULL)
+        KapaSendLabel (kapa, "Raw Star Luminosity Function", KAPA_LABEL_XP);
+    else
+        KapaSendLabel (kapa,
+                       "Reference Star Luminosity Function, Shifted Raw Fit, and Cutoff",
+                       KAPA_LABEL_XP);
+    graphdata.color=KapaColorByName("black");
+    graphdata.style = 1;
+    KapaPrepPlot (kapa, lnMag->n, &graphdata);
+    KapaPlotVector(kapa, lnMag->n,   Mag->data.F32, "x");
+    KapaPlotVector(kapa, lnMag->n, lnMag->data.F32, "y");
+
+    //Overplot fit
+    graphdata.style=0;
+    KapaPrepPlot(kapa,2,&graphdata);
+    KapaPlotVector(kapa, 2, x, "x");
+    KapaPlotVector(kapa, 2, y, "y");
+
+    //If rawFunc was supplied, calculate the cutoff magnitude
+    if( rawFunc != NULL) {
+        double mRef = 0.5*(lumFunc->mMin + lumFunc->mMax);
+        double logRho = mRef * lumFunc->slope + lumFunc->offset;
+        double mRaw = (logRho - rawFunc->offset) / rawFunc->slope;
+        double deltaM = mRef - mRaw;
+        double mRefMax = rawFunc->mMax + deltaM;
+
+        float xraw[2] = {rawFunc->mMin + deltaM, rawFunc->mMax + deltaM};
+        float yraw[2] = {rawFunc->offset + (rawFunc->slope) * rawFunc->mMin,
+                        rawFunc->offset + (rawFunc->slope) * rawFunc->mMax};
+        float x[2] = {mRefMax, mRefMax};
+        float y[2] = {graphdata.ymin, graphdata.ymax};
+        graphdata.color= KapaColorByName("red");
+        KapaPrepPlot(kapa, 2, &graphdata);
+        KapaPlotVector(kapa, 2, x, "x");
+        KapaPlotVector(kapa, 2, y, "y");
+        KapaPrepPlot (kapa, 2, &graphdata);
+        KapaPlotVector (kapa, 2, xraw, "x");
+        KapaPlotVector (kapa, 2, yraw, "y");
+
+        KapaPNG(kapa,"LumPlot.png");
+        //char c;
+        //fscanf(stdin, "%c", &c);
+        //        pmKapaClose(kapa);
+    }
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMaskUpdates.Mosaic.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMaskUpdates.Mosaic.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMaskUpdates.Mosaic.c	(revision 23321)
@@ -0,0 +1,459 @@
+# include "psastroInternal.h"
+
+# define ESCAPE { \
+  psError(PS_ERR_UNKNOWN, false, "I/O failure in psastroMaskUpdate"); \
+  psFree (view); \
+  return false; \
+}
+  
+pmCell *pmCellInChip (pmChip *chip, float x, float y);
+bool pmCellCoordsForChip (float *xCell, float *yCell, pmCell *cell, float xChip, float yChip);
+bool psastroMaskCircle (psImage *mask, psImageMaskType value, float x0, float y0, float dX, float dY);
+bool psastroMaskBox (psImage *mask, psImageMaskType value, float x0, float y0, float dL, float dW, float theta);
+void psastroMaskLine (psImage *mask, psImageMaskType value, double x1, double y1, double x2, double y2, int dW);
+void psastroMaskLineBresen (psImage *mask, psImageMaskType value, int X1, int Y1, int X2, int Y2, int dW, int swapcoords);
+void psastroMaskRectangle (psImage *mask, psImageMaskType value, int x0, int y0, int x1, int y1);
+
+// create a mask or mask regions based on the collection of reference stars that are 
+// in the vicinity of each chip
+bool psastroMaskUpdates (pmConfig *config) {
+
+    bool status;
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+    float zeropt, exptime;
+
+    psImageMaskType maskValue  = pmConfigMaskGet("GHOST", config); // Mask value for ghost pixels
+    psImageMaskType maskBlank  = pmConfigMaskGet("BLANK", config); // Mask value for blank pixels
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe");
+        return false;
+    }
+
+    // this step is optional
+    bool REFSTAR_MASK                      = psMetadataLookupBool (&status, recipe, "REFSTAR_MASK");
+    if (!REFSTAR_MASK) return true;
+
+    double REFSTAR_MASK_MAX_MAG            = psMetadataLookupF32 (&status, recipe, "REFSTAR_MASK_MAX_MAG");
+    double REFSTAR_MASK_SATSTAR_MAG_SLOPE  = psMetadataLookupF32 (&status, recipe, "REFSTAR_MASK_SATSTAR_MAG_SLOPE");
+    double REFSTAR_MASK_SATSTAR_MAG_MAX    = psMetadataLookupF32 (&status, recipe, "REFSTAR_MASK_SATSTAR_MAG_MAX");
+    double REFSTAR_MASK_SATSTAR_POS_ZERO   = psMetadataLookupF32 (&status, recipe, "REFSTAR_MASK_SATSTAR_POS_ZERO");
+    double REFSTAR_MASK_SATSPIKE_MAG_SLOPE = psMetadataLookupF32 (&status, recipe, "REFSTAR_MASK_SATSPIKE_MAG_SLOPE");
+    double REFSTAR_MASK_SATSPIKE_MAG_MAX   = psMetadataLookupF32 (&status, recipe, "REFSTAR_MASK_SATSPIKE_MAG_MAX");
+    double REFSTAR_MASK_SATSPIKE_WIDTH     = psMetadataLookupF32 (&status, recipe, "REFSTAR_MASK_SATSPIKE_WIDTH");
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find input data");
+	return false;
+    }
+    pmFPA *fpa = input->fpa;
+
+    // really error-out here?  or just skip?
+    if (!psastroZeroPointFromRecipe (&zeropt, &exptime, fpa, recipe)) {
+	psLogMsg ("psastro", PS_LOG_INFO, "failed to load zeropt data from recipe");
+	return false;
+    }
+
+    // recipe values are given in instrumental magnitudes
+    // use the zero point and exposure time to convert to apparent mags: M_ap = M_inst + C_0 + 2.5*log(exptime)
+    float MagOffset = zeropt + 2.5*log10(exptime);
+    REFSTAR_MASK_MAX_MAG += MagOffset;
+    REFSTAR_MASK_SATSTAR_MAG_MAX += MagOffset;
+    REFSTAR_MASK_SATSPIKE_MAG_MAX += MagOffset;
+
+    // select the input mask image :: we modify those pixels, the mosaic to chip mosaic format
+    pmFPAfile *inMask = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT.MASK");
+    if (!inMask) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find input mask");
+	return false;
+    }
+    pmFPA *fpaMask = inMask->fpa;
+
+    // select the output mask image :: we mosaic to chip mosaic format
+    pmFPAfile *outMask = psMetadataLookupPtr (NULL, config->files, "PSASTRO.OUTPUT.MASK");
+    if (!outMask) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find output mask");
+	return false;
+    }
+
+    // select the reference mask fpa :: we use this to determine cell boundaries
+    pmFPAfile *refMask = psMetadataLookupPtr (NULL, config->files, "PSASTRO.REFMASK");
+    if (!refMask) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find mask reference");
+	return false;
+    }
+
+    double POSANGLE = PM_RAD_DEG * psMetadataLookupF64 (&status, fpa->concepts, "FPA.POSANGLE"); 
+    psAssert (status, "POSANGLE missing");
+
+    // de-activate all files except PSASTRO.INPUT.MASK and PSASTRO.OUTPUT.MASK
+    pmFPAfileActivate (config->files, false, NULL);
+    pmFPAfileActivate (config->files, true, "PSASTRO.INPUT.MASK");
+    pmFPAfileActivate (config->files, true, "PSASTRO.OUTPUT.MASK");
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmFPAview *viewMask = pmFPAviewAlloc (0);
+
+    // open/load files as needed
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	if (!chip->fromFPA) { continue; }
+
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+
+	char *filename = NULL;
+	char *chipname = psMetadataLookupStr (&status, chip->concepts, "CHIP.NAME");
+	psStringAppend (&filename, "refstars.mask.%s.dat", chipname);
+	FILE *f = fopen (filename, "w");
+	if (!f) {
+	    psWarning ("cannot create refstar mask file %s\n", filename);
+	    continue;
+	}
+	psFree (filename);
+
+	pmChip *chipMask = pmFPAviewThisChip (view, fpaMask);
+
+	// load sequence for mask corresponding to this chip (XXX this is needed if the input mask is not the same format as the astrometry file
+	*viewMask = *view;
+        while ((cell = pmFPAviewNextCell (viewMask, fpaMask, 1)) != NULL) {
+            psTrace ("psastro", 4, "Mask Cell %d: %x %x\n", viewMask->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+	    if (!pmFPAfileIOChecks (config, viewMask, PM_FPA_BEFORE)) ESCAPE;
+	    
+            while ((readout = pmFPAviewNextReadout (viewMask, fpaMask, 1)) != NULL) {
+		if (!pmFPAfileIOChecks (config, viewMask, PM_FPA_BEFORE)) ESCAPE;
+                if (! readout->data_exists) { continue; }
+	    }
+	}
+
+        while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 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
+            // XXX there can only be one readout per chip in astrometry, right?
+            while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+                if (! readout->data_exists) { continue; }
+
+                // select the raw objects for this readout
+                psArray *refstars = psMetadataLookupPtr (&status, readout->analysis, "PSASTRO.REFSTARS");
+                if (refstars == NULL) { continue; }
+
+		// we need to generate the following masks regions:
+		// 1) circle around the saturated stars (scaled by magnitude)
+		// 2) diffraction spikes in direction ROT - ROTo
+		// 3) bleed trail in the direction of the readout
+
+		// XXX for the moment, let's just generate mana region-file objects in chip pixel space
+		for (int i = 0; i < refstars->n; i++) {
+		    pmAstromObj *ref = refstars->data[i];
+		    if (ref->Mag > REFSTAR_MASK_MAX_MAG) continue;
+		    
+		    // convert x,y chip coordinates to cells in maskChip
+		    pmCell *maskCell = pmCellInChip (chipMask, ref->chip->x, ref->chip->y);
+
+		    // XXX convert ref->Mag to instrumental mags
+
+		    // CIRCLE around the stars (scaled by magnitude)
+		    float radius = REFSTAR_MASK_SATSTAR_MAG_SLOPE * (REFSTAR_MASK_SATSTAR_MAG_MAX - ref->Mag);
+		    fprintf (f, "CIRCLE %f %f  %f %f\n", ref->chip->x, ref->chip->y, radius, radius);
+
+		    if (maskCell) {
+			float xCell = 0.0;
+			float yCell = 0.0;
+			pmCellCoordsForChip (&xCell, &yCell, maskCell, ref->chip->x, ref->chip->y);
+			// XXX for now, assume cell binning is 1x1 relative to chip
+			if (maskCell->readouts->n) {
+			    pmReadout *readout = maskCell->readouts->data[0];
+			    psastroMaskCircle (readout->mask, maskValue, xCell, yCell, radius, radius);
+			}
+		    }
+
+		    // LINE for boundaries of the saturation spikes (scaled by magnitude)
+		    float spikeLength = REFSTAR_MASK_SATSPIKE_MAG_SLOPE * (REFSTAR_MASK_SATSPIKE_MAG_MAX - ref->Mag);
+		    float spikeWidth = REFSTAR_MASK_SATSPIKE_WIDTH;
+
+		    for (float theta = 0.0; theta < 2*M_PI; theta += M_PI / 2.0) {
+			float x0, y0, x1, y1, dx, dy;
+
+			float Theta = POSANGLE - REFSTAR_MASK_SATSTAR_POS_ZERO + theta;
+
+			// lower side 
+			x0 = ref->chip->x + spikeWidth*sin(Theta);
+			y0 = ref->chip->y - spikeWidth*cos(Theta);
+			x1 = ref->chip->x + spikeLength*cos(Theta) + spikeWidth*sin(Theta);
+			y1 = ref->chip->y + spikeLength*sin(Theta) - spikeWidth*cos(Theta);
+			dx = x1 - x0;
+			dy = y1 - y0;
+			fprintf (f, "LINE %f %f  %f %f\n", x0, y0, dx, dy);
+
+			// upper side 
+			x0 = ref->chip->x - spikeWidth*sin(Theta);
+			y0 = ref->chip->y + spikeWidth*cos(Theta);
+			x1 = ref->chip->x + spikeLength*cos(Theta) - spikeWidth*sin(Theta);
+			y1 = ref->chip->y + spikeLength*sin(Theta) + spikeWidth*cos(Theta);
+			dx = x1 - x0;
+			dy = y1 - y0;
+			fprintf (f, "LINE %f %f  %f %f\n", x0, y0, dx, dy);
+
+			if (maskCell) {
+			    float xCell = 0.0;
+			    float yCell = 0.0;
+			    pmCellCoordsForChip (&xCell, &yCell, maskCell, ref->chip->x, ref->chip->y);
+			    int xParityCell = psMetadataLookupS32(NULL, maskCell->concepts, "CELL.XPARITY");
+			    int yParityCell = psMetadataLookupS32(NULL, maskCell->concepts, "CELL.YPARITY");
+			    // XXX for now, assume cell binning is 1x1 relative to chip
+			    // XXX for now, assume only a single readout
+			    if (maskCell->readouts->n) {
+				pmReadout *readout = maskCell->readouts->data[0];
+				psastroMaskBox (readout->mask, maskValue, xCell, yCell, spikeLength, spikeWidth, Theta*xParityCell*yParityCell);
+			    }
+			}
+		    }
+
+		    // LINE for boundaries of the bleed lines
+		    fprintf (f, "LINE %f %f  %f %f\n", ref->chip->x, ref->chip->y, 0.0, -100.0);
+		    if (maskCell) {
+			float xCell = 0.0;
+			float yCell = 0.0;
+			pmCellCoordsForChip (&xCell, &yCell, maskCell, ref->chip->x, ref->chip->y);
+			// XXX for now, assume cell binning is 1x1 relative to chip
+			if (maskCell->readouts->n) {
+			    pmReadout *readout = maskCell->readouts->data[0];
+			    psastroMaskRectangle (readout->mask, maskValue, (int) xCell-3, (int) yCell, (int) xCell+4, readout->mask->numRows);
+			    // XXX this can be done more easily knowing the we mask to the end in y only
+			}
+		    }
+		}
+            }
+        }
+
+	pmChip *outChip = pmFPAviewThisChip(view, outMask->fpa);
+	if (!outChip->hdu && !outChip->parent->hdu) {
+	    const char *name = psMetadataLookupStr(&status, inMask->fpa->concepts, "FPA.OBS"); // Name of FPA
+	    pmFPAAddSourceFromView(outMask->fpa, name, view, outMask->format);
+	}
+
+	psTrace("psastro", 5, "mosaic chip %s to %s (xbin,ybin: %d,%d to %d,%d)\n",
+		inMask->name, outMask->name, inMask->xBin, inMask->yBin, outMask->xBin, outMask->yBin);
+
+	// Mosaic the chip, making a deep copy.  This has the side effect of making the output
+	// image products pure trimmed images, but also increases the memory footprint.
+	status = pmChipMosaic(outChip, chipMask, true, maskBlank);
+
+	// output sequence for mask corresponding to this chip
+	*viewMask = *view;
+        while ((cell = pmFPAviewNextCell (viewMask, outMask->fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Mask Cell %d: %x %x\n", viewMask->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+	    
+            while ((readout = pmFPAviewNextReadout (viewMask, outMask->fpa, 1)) != NULL) {
+                if (! readout->data_exists) { continue; }
+		if (!pmFPAfileIOChecks (config, viewMask, PM_FPA_AFTER)) ESCAPE;
+	    }
+	    if (!pmFPAfileIOChecks (config, viewMask, PM_FPA_AFTER)) ESCAPE;
+	}
+
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE;
+	fclose (f);
+    }
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE;
+
+    // deactivate all files
+    pmFPAfileActivate (config->files, false, NULL);
+
+    psFree (view);
+    psFree (viewMask);
+    return true;
+}
+
+// XXX this is going to be very slow...
+pmCell *pmCellInChip (pmChip *chip, float x, float y) {
+
+# if (0)
+    int x0Cell = psMetadataLookupS32(NULL, cell->concepts, "CELL.X0");
+    int y0Cell = psMetadataLookupS32(NULL, cell->concepts, "CELL.Y0");
+    int xParityCell = psMetadataLookupS32(NULL, cell->concepts, "CELL.XPARITY");
+    int yParityCell = psMetadataLookupS32(NULL, cell->concepts, "CELL.YPARITY");
+
+    // XXX fix the binning : currently not selected from concepts
+    // int xBin = psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN"); // Binning in x and y
+    // int yBin = psMetadataLookupS32(NULL, cell->concepts, "CELL.YBIN"); // Binning in x and y
+    int xBin = 1;
+    int yBin = 1;
+
+    // Position on the cell 
+    float xCell = PM_CHIP_TO_CELL(xChip, x0Cell, xParityCell, xBin);
+    float yCell = PM_CHIP_TO_CELL(yChip, y0Cell, yParityCell, yBin);
+# endif
+
+    for (int i = 0; i < chip->cells->n; i++) {
+
+	pmCell *cell = chip->cells->data[i];
+	psRegion *region = pmCellExtent (cell);
+
+	if (x < region->x0) goto skip;
+	if (x > region->x1) goto skip;
+	if (y < region->y0) goto skip;
+	if (y > region->y1) goto skip;
+
+	psFree (region);
+	return cell;
+
+    skip:
+	psFree (region);
+    }
+    return NULL;
+}
+
+// convert chip coords to cell coords, given known cell
+bool pmCellCoordsForChip (float *xCell, float *yCell, pmCell *cell, float xChip, float yChip) {
+
+    int x0Cell = psMetadataLookupS32(NULL, cell->concepts, "CELL.X0");
+    int y0Cell = psMetadataLookupS32(NULL, cell->concepts, "CELL.Y0");
+    int xParityCell = psMetadataLookupS32(NULL, cell->concepts, "CELL.XPARITY");
+    int yParityCell = psMetadataLookupS32(NULL, cell->concepts, "CELL.YPARITY");
+
+    // XXX fix the binning : currently not selected from concepts
+    // int xBin = psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN"); // Binning in x and y
+    // int yBin = psMetadataLookupS32(NULL, cell->concepts, "CELL.YBIN"); // Binning in x and y
+    int xBin = 1;
+    int yBin = 1;
+
+    // Position on the cell 
+    // ((pos)*(binning)*(cellParity) + (cell0))
+    // XXX this is probably totally wrong now....
+    // ((pos) - (cell0))*(cellParity)/(binning))
+    *xCell = (xChip - x0Cell)*xParityCell/xBin;
+    *yCell = (yChip - y0Cell)*yParityCell/yBin;
+
+    return true;
+}
+
+// XXX should be doing an OR
+bool psastroMaskCircle (psImage *mask, psImageMaskType value, float x0, float y0, float dX, float dY) {
+
+    // XXX need to worry about row0, col0
+    for (int ix = -dX; ix <= +dX; ix++) {
+	int jx = ix + x0;
+	if (jx < 0) continue;
+	if (jx >= mask->numCols) continue;
+	for (int iy = -dY; iy <= +dY; iy++) {
+	    int jy = iy + y0;
+	    if (jy < 0) continue;
+	    if (jy >= mask->numRows) continue;
+
+	    double r2 = PS_SQR(ix/dX) + PS_SQR(iy/dY);
+	    if (r2 > 1.0) continue;
+	    
+	    mask->data.PS_TYPE_IMAGE_MASK_DATA[jy][jx] |= value;
+	}
+    }
+    return true;
+}
+
+// XXX should be doing an OR
+bool psastroMaskBox (psImage *mask, psImageMaskType value, float x0, float y0, float dL, float dW, float theta) {
+
+    // draw a series of lines (from -0.5*dW to +0.5*dW) of length dL, starting at x0, y0, angle theta
+
+    float xs = x0;
+    float ys = y0;
+
+    float xe = xs + dL*cos(theta);
+    float ye = ys + dL*sin(theta);
+
+    psastroMaskLine (mask, value, xs, ys, xe, ye, (int) dW);
+    return true;
+}
+
+// identify the quadrant and draw the correct line
+void psastroMaskLine (psImage *mask, psImageMaskType value, double x1, double y1, double x2, double y2, int dW) {
+
+  int FlipDirect, FlipCoords;
+  int X1, Y1, X2, Y2, dX, dY;
+
+  /* rather than draw the line from float positions, we find the closest
+     integer end-points and draw the line between those pixels */ 
+
+  X1 = ROUND(x1);
+  Y1 = ROUND(y1);
+  X2 = ROUND(x2);
+  Y2 = ROUND(y2);
+
+  dX = X2 - X1;
+  dY = Y2 - Y1;
+
+  FlipCoords = (abs(dX) < abs(dY));
+  FlipDirect = FlipCoords ? (y1 > y2) : (x1 > x2);
+
+  if (!FlipDirect && !FlipCoords) psastroMaskLineBresen (mask, value, X1, Y1, X2, Y2, dW, FALSE);
+  if ( FlipDirect && !FlipCoords) psastroMaskLineBresen (mask, value, X2, Y2, X1, Y1, dW, FALSE);
+  if (!FlipDirect &&  FlipCoords) psastroMaskLineBresen (mask, value, Y1, X1, Y2, X2, dW, TRUE);
+  if ( FlipDirect &&  FlipCoords) psastroMaskLineBresen (mask, value, Y2, X2, Y1, X1, dW, TRUE);
+
+  return;
+}
+
+// use the Bresenham line drawing technique
+// integer-only Bresenham line-draw version which is fast
+void psastroMaskLineBresen (psImage *mask, psImageMaskType value, int X1, int Y1, int X2, int Y2, int dW, int swapcoords) {
+
+    int X, Y, dX, dY;
+    int e, e2;
+
+    dX = X2 - X1;
+    dY = Y2 - Y1;
+
+    Y = Y1;
+    e = 0;
+    for (X = X1; X <= X2; X++) {
+	if (X > 0) {
+	    if (swapcoords) {
+		if (X >= mask->numRows) continue;
+		for (int y = Y - dW; y <= Y + dW; y++) {
+		    if (y < 0) continue;
+		    if (y >= mask->numCols) continue;
+		    mask->data.PS_TYPE_IMAGE_MASK_DATA[X][y] |= value;
+		}
+	    } else {
+		if (X >= mask->numCols) continue;
+		for (int y = Y - dW; y <= Y + dW; y++) {
+		    if (y < 0) continue;
+		    if (y >= mask->numRows) continue;
+		    mask->data.PS_TYPE_IMAGE_MASK_DATA[y][X] |= value;
+		}
+	    }
+	}
+	e += dY;
+	e2 = 2 * e;
+	if (e2 > dX) {
+	    Y++;
+	    e -= dX;
+	} 
+	if (e2 < -dX) {
+	    Y--;
+	    e += dX;
+	}
+    }
+    return;
+}
+
+void psastroMaskRectangle (psImage *mask, psImageMaskType value, int x0, int y0, int x1, int y1) {
+    for (int iy = PS_MAX(0,y0); iy < PS_MIN(y1,mask->numRows); iy++) {
+	for (int ix = PS_MAX(0,x0); ix < PS_MIN(x1,mask->numCols); ix++) {
+	    mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= value;
+	}
+    }
+}
+
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMaskUpdates.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMaskUpdates.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMaskUpdates.c	(revision 23321)
@@ -0,0 +1,520 @@
+/** @file psastroMaskUpdates.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+# define ESCAPE { \
+  psError(PS_ERR_UNKNOWN, false, "I/O failure in psastroMaskUpdate"); \
+  psFree (view); \
+  return false; \
+}
+
+pmCell *pmCellInChip (pmChip *chip, float x, float y);
+bool pmCellCoordsForChip (float *xCell, float *yCell, pmCell *cell, float xChip, float yChip);
+bool pmChipCoordsForCell (float *xChip, float *yChip, pmCell *cell, float xCell, float yCell);
+
+bool psastroMaskCircle (psImage *mask, psImageMaskType value, float x0, float y0, float dX, float dY);
+bool psastroMaskBox (psImage *mask, psImageMaskType value, float x0, float y0, float dL, float dW, float theta);
+void psastroMaskLine (psImage *mask, psImageMaskType value, double x1, double y1, double x2, double y2, int dW);
+void psastroMaskLineBresen (psImage *mask, psImageMaskType value, int X1, int Y1, int X2, int Y2, int dW, int swapcoords);
+void psastroMaskRectangle (psImage *mask, psImageMaskType value, int x0, int y0, int x1, int y1);
+
+/**
+ * create a mask or mask regions based on the collection of reference stars that * are in the vicinity of each chip
+ */
+bool psastroMaskUpdates (pmConfig *config) {
+
+    bool status;
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+    float zeropt, exptime;
+
+    psImageMaskType maskValue  = pmConfigMaskGet("GHOST", config); // Mask value for ghost pixels
+
+    // psImageMaskType maskBlank  = pmConfigMaskGet("BLANK", config); // Mask value for blank pixels
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe");
+        return false;
+    }
+
+    // this step is optional
+    bool REFSTAR_MASK                      = psMetadataLookupBool (&status, recipe, "REFSTAR_MASK");
+    if (!REFSTAR_MASK) return true;
+
+    psLogMsg ("psastro", PS_LOG_INFO, "generating a bright-star mask");
+
+    bool REFSTAR_MASK_REGIONS              = psMetadataLookupBool (&status, recipe, "REFSTAR_MASK_REGIONS");
+
+    double REFSTAR_MASK_MAX_MAG            = psMetadataLookupF32 (&status, recipe, "REFSTAR_MASK_MAX_MAG");
+    double REFSTAR_MASK_SATSTAR_MAG_SLOPE  = psMetadataLookupF32 (&status, recipe, "REFSTAR_MASK_SATSTAR_MAG_SLOPE");
+    double REFSTAR_MASK_SATSTAR_MAG_MAX    = psMetadataLookupF32 (&status, recipe, "REFSTAR_MASK_SATSTAR_MAG_MAX");
+    double REFSTAR_MASK_SATSTAR_POS_ZERO   = psMetadataLookupF32 (&status, recipe, "REFSTAR_MASK_SATSTAR_POS_ZERO");
+    double REFSTAR_MASK_SATSPIKE_MAG_SLOPE = psMetadataLookupF32 (&status, recipe, "REFSTAR_MASK_SATSPIKE_MAG_SLOPE");
+    double REFSTAR_MASK_SATSPIKE_MAG_MAX   = psMetadataLookupF32 (&status, recipe, "REFSTAR_MASK_SATSPIKE_MAG_MAX");
+    double REFSTAR_MASK_SATSPIKE_WIDTH     = psMetadataLookupF32 (&status, recipe, "REFSTAR_MASK_SATSPIKE_WIDTH");
+    double REFSTAR_MASK_BLEED_MAG_MAX      = psMetadataLookupF32 (&status, recipe, "REFSTAR_MASK_BLEED_MAG_MAX");
+    double REFSTAR_MASK_BLEED_MAG_SLOPE    = psMetadataLookupF32 (&status, recipe, "REFSTAR_MASK_BLEED_MAG_SLOPE");
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find input data");
+        return false;
+    }
+    pmFPA *fpa = input->fpa;
+
+    // really error-out here?  or just skip?
+    if (!psastroZeroPointFromRecipe (&zeropt, &exptime, fpa, recipe)) {
+        psLogMsg ("psastro", PS_LOG_INFO, "failed to load zeropt data from recipe");
+        return false;
+    }
+
+    // recipe values are given in instrumental magnitudes
+    // use the zero point and exposure time to convert to apparent mags: M_ap = M_inst + C_0 + 2.5*log(exptime)
+    float MagOffset = zeropt + 2.5*log10(exptime);
+    REFSTAR_MASK_MAX_MAG += MagOffset;
+    REFSTAR_MASK_SATSTAR_MAG_MAX += MagOffset;
+    REFSTAR_MASK_SATSPIKE_MAG_MAX += MagOffset;
+    REFSTAR_MASK_BLEED_MAG_MAX += MagOffset;
+
+    // select the output mask image :: we mosaic to chip mosaic format
+    pmFPAfile *outMask = psMetadataLookupPtr (NULL, config->files, "PSASTRO.OUTPUT.MASK");
+    if (!outMask) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find output mask");
+        return false;
+    }
+    pmFPA *fpaMask = outMask->fpa;
+
+    // select the reference mask fpa :: we use this to determine cell boundaries
+    pmFPAfile *refMask = psMetadataLookupPtr (NULL, config->files, "PSASTRO.REFMASK");
+    if (!refMask) {
+        psError(PSASTRO_ERR_CONFIG, true, "Can't find mask reference");
+        return false;
+    }
+
+    psLogMsg ("psastro", PS_LOG_INFO, "loading output and reference mask files");
+
+    // double POSANGLE = PM_RAD_DEG * psMetadataLookupF64 (&status, fpa->concepts, "FPA.POSANGLE");
+    // psAssert (status, "POSANGLE missing");
+    double ROTANGLE = PM_RAD_DEG * psMetadataLookupF64 (&status, fpa->concepts, "FPA.ROTANGLE");
+    psAssert (status, "ROTANGLE missing");
+
+    // de-activate all files except PSASTRO.INPUT.MASK and PSASTRO.OUTPUT.MASK
+    pmFPAfileActivate (config->files, false, NULL);
+    pmFPAfileActivate (config->files, true, "PSASTRO.INPUT.MASK");
+    pmFPAfileActivate (config->files, true, "PSASTRO.OUTPUT.MASK");
+    pmFPAfileActivate (config->files, true, "PSASTRO.REFMASK");
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmFPAview *viewMask = pmFPAviewAlloc (0);
+
+    // open/load files as needed
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+        if (!chip->fromFPA) { continue; }
+
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+
+        // text region files for testing
+        FILE *f = NULL;
+        if (REFSTAR_MASK_REGIONS) {
+          char *filename = NULL;
+          char *chipname = psMetadataLookupStr (&status, chip->concepts, "CHIP.NAME");
+          psStringAppend (&filename, "refstars.mask.%s.dat", chipname);
+          FILE *f = fopen (filename, "w");
+          if (!f) {
+            psWarning ("cannot create refstar mask file %s\n", filename);
+            continue;
+          }
+          psFree (filename);
+        }
+
+        pmChip *refChip  = pmFPAviewThisChip (view, refMask->fpa);
+
+        // load sequence for mask corresponding to this chip (XXX this is needed if the input mask is not the same format as the astrometry file
+        *viewMask = *view;
+        while ((cell = pmFPAviewNextCell (viewMask, fpaMask, 1)) != NULL) {
+            psTrace ("psastro", 4, "Mask Cell %d: %x %x\n", viewMask->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+            if (!pmFPAfileIOChecks (config, viewMask, PM_FPA_BEFORE)) ESCAPE;
+
+            while ((readout = pmFPAviewNextReadout (viewMask, fpaMask, 1)) != NULL) {
+                if (!pmFPAfileIOChecks (config, viewMask, PM_FPA_BEFORE)) ESCAPE;
+                if (! readout->data_exists) { continue; }
+            }
+        }
+
+        while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+            // the input mask is a chip-mosaic image
+            // the output mask is a chip-mosaic image
+            // we mark the masked pixels in the chip space, BUT
+            // we need to find the ends of the cells for the bleeds
+
+            // we mask pixels on the input mask image (chip-mosaic)
+            pmCell *cellMask = pmFPAviewThisCell(view, outMask->fpa);
+            pmReadout *readoutMask = NULL;
+            if (cellMask->readouts->n) {
+                readoutMask = cellMask->readouts->data[0];
+            }
+
+            // process each of the readouts
+            // XXX there can only be one readout per chip in astrometry, right?
+            while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+                if (! readout->data_exists) { continue; }
+
+                // select the raw objects for this readout
+                psArray *refstars = psMetadataLookupPtr (&status, readout->analysis, "PSASTRO.REFSTARS");
+                if (refstars == NULL) { continue; }
+
+                // we need to generate the following masks regions:
+                // 1) circle around the saturated stars (scaled by magnitude)
+                // 2) diffraction spikes in direction ROT - ROTo
+                // 3) bleed trail in the direction of the readout
+
+                // XXX for the moment, let's just generate mana region-file objects in chip pixel space
+                for (int i = 0; i < refstars->n; i++) {
+                    pmAstromObj *ref = refstars->data[i];
+                    if (ref->Mag > REFSTAR_MASK_MAX_MAG) continue;
+
+                    // XXX convert ref->Mag to instrumental mags
+
+                    // CIRCLE around the stars (scaled by magnitude)
+                    float radius = REFSTAR_MASK_SATSTAR_MAG_SLOPE * (REFSTAR_MASK_SATSTAR_MAG_MAX - ref->Mag);
+
+                    if (REFSTAR_MASK_REGIONS) {
+                      fprintf (f, "CIRCLE %f %f  %f %f\n", ref->chip->x, ref->chip->y, radius, radius);
+                    }
+
+                    // XXX for now, assume cell binning is 1x1 relative to chip
+                    if (readoutMask) {
+                        psastroMaskCircle (readoutMask->mask, maskValue, ref->chip->x, ref->chip->y, radius, radius);
+                    }
+
+                    // LINE for boundaries of the saturation spikes (scaled by magnitude)
+                    float spikeLength = REFSTAR_MASK_SATSPIKE_MAG_SLOPE * (REFSTAR_MASK_SATSPIKE_MAG_MAX - ref->Mag);
+                    float spikeWidth = REFSTAR_MASK_SATSPIKE_WIDTH;
+
+                    for (float theta = 0.0; theta < 2*M_PI; theta += M_PI / 2.0) {
+                        float x0, y0, x1, y1, dx, dy;
+
+                        float Theta = theta - ROTANGLE - REFSTAR_MASK_SATSTAR_POS_ZERO;
+
+                        if (REFSTAR_MASK_REGIONS) {
+                          // lower side
+                          x0 = ref->chip->x + spikeWidth*sin(Theta);
+                          y0 = ref->chip->y - spikeWidth*cos(Theta);
+                          x1 = ref->chip->x + spikeLength*cos(Theta) + spikeWidth*sin(Theta);
+                          y1 = ref->chip->y + spikeLength*sin(Theta) - spikeWidth*cos(Theta);
+                          dx = x1 - x0;
+                          dy = y1 - y0;
+
+                          fprintf (f, "LINE %f %f  %f %f\n", x0, y0, dx, dy);
+
+                          // upper side
+                          x0 = ref->chip->x - spikeWidth*sin(Theta);
+                          y0 = ref->chip->y + spikeWidth*cos(Theta);
+                          x1 = ref->chip->x + spikeLength*cos(Theta) - spikeWidth*sin(Theta);
+                          y1 = ref->chip->y + spikeLength*sin(Theta) + spikeWidth*cos(Theta);
+                          dx = x1 - x0;
+                          dy = y1 - y0;
+                          fprintf (f, "LINE %f %f  %f %f\n", x0, y0, dx, dy);
+                        }
+
+                        if (readoutMask) {
+                            psastroMaskBox (readoutMask->mask, maskValue, ref->chip->x, ref->chip->y, spikeLength, spikeWidth, Theta);
+                        }
+                    }
+
+                    // convert x,y chip coordinates to cells in maskChip
+                    pmCell *refCell = pmCellInChip (refChip, ref->chip->x, ref->chip->y);
+
+                    // LINE for boundaries of the bleed lines
+                    if (REFSTAR_MASK_REGIONS) {
+                      fprintf (f, "LINE %f %f  %f %f\n", ref->chip->x, ref->chip->y, 0.0, -100.0);
+                    }
+
+                    if (readoutMask && refCell) {
+                        float xCell = 0.0;
+                        float yCell = 0.0;
+                        float xEnd = 0.0;
+                        float yEnd = 0.0;
+                        // find coordinate of star on cell
+                        pmCellCoordsForChip (&xCell, &yCell, refCell, ref->chip->x, ref->chip->y);
+                        // find coordinate of end-point on chip
+
+                        int ySize = psMetadataLookupS32(NULL, refCell->concepts, "CELL.YSIZE");
+                        pmChipCoordsForCell (&xEnd, &yEnd, refCell, xCell, ySize);
+
+                        float width = REFSTAR_MASK_BLEED_MAG_SLOPE*(REFSTAR_MASK_BLEED_MAG_MAX - ref->Mag);
+                        psastroMaskRectangle (readoutMask->mask, maskValue, (int) ref->chip->x-0.5*width, (int) ref->chip->y, (int) ref->chip->x+0.5*width+1, yEnd);
+                    }
+                }
+            }
+        }
+
+        // output sequence for mask corresponding to this chip
+        *viewMask = *view;
+        while ((cell = pmFPAviewNextCell (viewMask, outMask->fpa, 1)) != NULL) {
+            psTrace ("psastro", 4, "Mask Cell %d: %x %x\n", viewMask->cell, cell->file_exists, cell->process);
+            if (!cell->process || !cell->file_exists) { continue; }
+
+            while ((readout = pmFPAviewNextReadout (viewMask, outMask->fpa, 1)) != NULL) {
+                if (! readout->data_exists) { continue; }
+                if (!pmFPAfileIOChecks (config, viewMask, PM_FPA_AFTER)) ESCAPE;
+            }
+            if (!pmFPAfileIOChecks (config, viewMask, PM_FPA_AFTER)) ESCAPE;
+        }
+
+        if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE;
+        if (REFSTAR_MASK_REGIONS) {
+          fclose (f);
+        }
+    }
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE;
+
+    // deactivate all files
+    pmFPAfileActivate (config->files, false, NULL);
+
+    psFree (view);
+    psFree (viewMask);
+    return true;
+}
+
+// XXX this is going to be very slow...
+pmCell *pmCellInChip (pmChip *chip, float x, float y) {
+
+# if (0)
+    int x0Cell = psMetadataLookupS32(NULL, cell->concepts, "CELL.X0");
+    int y0Cell = psMetadataLookupS32(NULL, cell->concepts, "CELL.Y0");
+    int xParityCell = psMetadataLookupS32(NULL, cell->concepts, "CELL.XPARITY");
+    int yParityCell = psMetadataLookupS32(NULL, cell->concepts, "CELL.YPARITY");
+
+    // XXX fix the binning : currently not selected from concepts
+    // int xBin = psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN"); // Binning in x and y
+    // int yBin = psMetadataLookupS32(NULL, cell->concepts, "CELL.YBIN"); // Binning in x and y
+    int xBin = 1;
+    int yBin = 1;
+
+    // Position on the cell
+    float xCell = PM_CHIP_TO_CELL(xChip, x0Cell, xParityCell, xBin);
+    float yCell = PM_CHIP_TO_CELL(yChip, y0Cell, yParityCell, yBin);
+# endif
+
+    for (int i = 0; i < chip->cells->n; i++) {
+
+        pmCell *cell = chip->cells->data[i];
+        psRegion *region = pmCellExtent (cell);
+
+        if (x < region->x0) goto skip;
+        if (x > region->x1) goto skip;
+        if (y < region->y0) goto skip;
+        if (y > region->y1) goto skip;
+
+        psFree (region);
+        return cell;
+
+    skip:
+        psFree (region);
+    }
+    return NULL;
+}
+
+/**
+ * convert chip coords to cell coords, given known cell
+ */
+bool pmCellCoordsForChip (float *xCell, float *yCell, pmCell *cell, float xChip, float yChip) {
+
+    int x0Cell = psMetadataLookupS32(NULL, cell->concepts, "CELL.X0");
+    int y0Cell = psMetadataLookupS32(NULL, cell->concepts, "CELL.Y0");
+    int xParityCell = psMetadataLookupS32(NULL, cell->concepts, "CELL.XPARITY");
+    int yParityCell = psMetadataLookupS32(NULL, cell->concepts, "CELL.YPARITY");
+
+    // XXX fix the binning : currently not selected from concepts
+    // int xBin = psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN"); // Binning in x and y
+    // int yBin = psMetadataLookupS32(NULL, cell->concepts, "CELL.YBIN"); // Binning in x and y
+    int xBin = 1;
+    int yBin = 1;
+
+    // Position on the cell
+    // ((pos)*(binning)*(cellParity) + (cell0))
+    // XXX this is probably totally wrong now....
+    // ((pos) - (cell0))*(cellParity)/(binning))
+    *xCell = (xChip - x0Cell)*xParityCell/xBin;
+    *yCell = (yChip - y0Cell)*yParityCell/yBin;
+
+    return true;
+}
+
+/**
+ * convert chip coords to cell coords, given known cell
+ */
+bool pmChipCoordsForCell (float *xChip, float *yChip, pmCell *cell, float xCell, float yCell) {
+
+    int x0Cell = psMetadataLookupS32(NULL, cell->concepts, "CELL.X0");
+    int y0Cell = psMetadataLookupS32(NULL, cell->concepts, "CELL.Y0");
+    int xParityCell = psMetadataLookupS32(NULL, cell->concepts, "CELL.XPARITY");
+    int yParityCell = psMetadataLookupS32(NULL, cell->concepts, "CELL.YPARITY");
+
+    // XXX fix the binning : currently not selected from concepts
+    // int xBin = psMetadataLookupS32(NULL, cell->concepts, "CELL.XBIN"); // Binning in x and y
+    // int yBin = psMetadataLookupS32(NULL, cell->concepts, "CELL.YBIN"); // Binning in x and y
+    int xBin = 1;
+    int yBin = 1;
+
+    // Position on the cell
+    // ((pos)*(binning)*(cellParity) + (cell0))
+    // XXX this is probably totally wrong now....
+    // ((pos) - (cell0))*(cellParity)/(binning))
+    *xChip = xCell*xBin*xParityCell + x0Cell;
+    *yChip = yCell*yBin*yParityCell + y0Cell;
+
+    return true;
+}
+
+// XXX should be doing an OR
+bool psastroMaskCircle (psImage *mask, psImageMaskType value, float x0, float y0, float dX, float dY) {
+
+    // XXX need to worry about row0, col0
+    for (int ix = -dX; ix <= +dX; ix++) {
+        int jx = ix + x0;
+        if (jx < 0) continue;
+        if (jx >= mask->numCols) continue;
+        for (int iy = -dY; iy <= +dY; iy++) {
+            int jy = iy + y0;
+            if (jy < 0) continue;
+            if (jy >= mask->numRows) continue;
+
+            double r2 = PS_SQR(ix/dX) + PS_SQR(iy/dY);
+            if (r2 > 1.0) continue;
+
+            mask->data.PS_TYPE_IMAGE_MASK_DATA[jy][jx] |= value;
+        }
+    }
+    return true;
+}
+
+// XXX should be doing an OR
+bool psastroMaskBox (psImage *mask, psImageMaskType value, float x0, float y0, float dL, float dW, float theta) {
+
+    // draw a series of lines (from -0.5*dW to +0.5*dW) of length dL, starting at x0, y0, angle theta
+
+    float xs = x0;
+    float ys = y0;
+
+    float xe = xs + dL*cos(theta);
+    float ye = ys + dL*sin(theta);
+
+    psastroMaskLine (mask, value, xs, ys, xe, ye, (int) dW);
+    return true;
+}
+
+/**
+ * identify the quadrant and draw the correct line
+ */
+void psastroMaskLine (psImage *mask, psImageMaskType value, double x1, double y1, double x2, double y2, int dW) {
+
+  int FlipDirect, FlipCoords;
+  int X1, Y1, X2, Y2, dX, dY;
+
+  /* rather than draw the line from float positions, we find the closest
+     integer end-points and draw the line between those pixels */
+
+  X1 = ROUND(x1);
+  Y1 = ROUND(y1);
+  X2 = ROUND(x2);
+  Y2 = ROUND(y2);
+
+  dX = X2 - X1;
+  dY = Y2 - Y1;
+
+  FlipCoords = (abs(dX) < abs(dY));
+  FlipDirect = FlipCoords ? (y1 > y2) : (x1 > x2);
+
+  if (!FlipDirect && !FlipCoords) psastroMaskLineBresen (mask, value, X1, Y1, X2, Y2, dW, FALSE);
+  if ( FlipDirect && !FlipCoords) psastroMaskLineBresen (mask, value, X2, Y2, X1, Y1, dW, FALSE);
+  if (!FlipDirect &&  FlipCoords) psastroMaskLineBresen (mask, value, Y1, X1, Y2, X2, dW, TRUE);
+  if ( FlipDirect &&  FlipCoords) psastroMaskLineBresen (mask, value, Y2, X2, Y1, X1, dW, TRUE);
+
+  return;
+}
+
+/**
+ * use the Bresenham line drawing technique
+ * integer-only Bresenham line-draw version which is fast
+ */
+void psastroMaskLineBresen (psImage *mask, psImageMaskType value, int X1, int Y1, int X2, int Y2, int dW, int swapcoords) {
+
+    int X, Y, dX, dY;
+    int e, e2;
+
+    dX = X2 - X1;
+    dY = Y2 - Y1;
+
+    Y = Y1;
+    e = 0;
+    for (X = X1; X <= X2; X++) {
+        if (X > 0) {
+            if (swapcoords) {
+                if (X >= mask->numRows) continue;
+                for (int y = Y - dW; y <= Y + dW; y++) {
+                    if (y < 0) continue;
+                    if (y >= mask->numCols) continue;
+                    mask->data.PS_TYPE_IMAGE_MASK_DATA[X][y] |= value;
+                }
+            } else {
+                if (X >= mask->numCols) continue;
+                for (int y = Y - dW; y <= Y + dW; y++) {
+                    if (y < 0) continue;
+                    if (y >= mask->numRows) continue;
+                    mask->data.PS_TYPE_IMAGE_MASK_DATA[y][X] |= value;
+                }
+            }
+        }
+        e += dY;
+        e2 = 2 * e;
+        if (e2 > dX) {
+            Y++;
+            e -= dX;
+        }
+        if (e2 < -dX) {
+            Y--;
+            e += dX;
+        }
+    }
+    return;
+}
+
+void psastroMaskRectangle (psImage *mask, psImageMaskType value, int x0, int y0, int x1, int y1) {
+
+    int xs = PS_MAX (0, PS_MIN (mask->numCols, PS_MIN (x0, x1)));
+    int xe = PS_MAX (0, PS_MIN (mask->numCols, PS_MAX (x0, x1)));
+    int ys = PS_MAX (0, PS_MIN (mask->numRows, PS_MIN (y0, y1)));
+    int ye = PS_MAX (0, PS_MIN (mask->numRows, PS_MAX (y0, y1)));
+
+    for (int iy = ys; iy < ye; iy++) {
+        for (int ix = xs; ix < xe; ix++) {
+            mask->data.PS_TYPE_IMAGE_MASK_DATA[iy][ix] |= value;
+        }
+    }
+}
+
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMetadataStats.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMetadataStats.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMetadataStats.c	(revision 23321)
@@ -0,0 +1,80 @@
+/** @file psastroMetaDataStats.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+bool psastroMetadataStats (pmConfig *config) {
+
+    bool status;
+
+    char *filename = psMetadataLookupStr (&status, config->arguments, "STATS");
+    if (!filename) return true; // stats not requested
+
+    // Extract statistics from the last output fpa
+    pmFPAfile *output = psMetadataLookupPtr(&status, config->files, "PSASTRO.OUTPUT");
+
+    if (!output) {
+	psError(PS_ERR_UNEXPECTED_NULL, true, "Unable to find output file (PSASTRO.OUTPUT).");
+	return false;
+    }
+
+    // create output stats metadata
+    psMetadata *stats = psMetadataAlloc ();
+
+    // extract stats for the complete fpa 
+    pmFPAview *view = pmFPAviewAlloc(0);
+
+    if (!ppStatsMetadata(stats, output->fpa, view, 0, config)) {
+	psError(PS_ERR_UNEXPECTED_NULL, false, "Unable to generate stats for image.");
+	psFree(view);
+	psFree(stats);
+	return false;
+    }
+
+    // if we did not request any specific stats, the structure is empty
+    if (stats && stats->list->n == 0) {
+	psWarning ("stats output specified, but no requested stats entries in headers");
+	psFree(view);
+	psFree(stats);
+	return true;
+    }
+
+    // convert the stats MDC to a string
+    char *statsMDC = psMetadataConfigFormat(stats);
+    if (!statsMDC || strlen(statsMDC) == 0) {
+	psError(PS_ERR_IO, false, "Unable to serialize stats metadata.\n");
+	return false;
+    } 
+
+    // convert to a real UNIX filename
+    psString resolved = pmConfigConvertFilename(filename, config, true, false); // Resolved filename
+    FILE *statsFile = fopen (resolved, "w");
+    if (!statsFile) {
+	psError(PS_ERR_IO, true, "Unable to open statistics file %s for writing.\n", resolved);
+	psFree(stats);
+	psFree(view);
+	psFree(statsMDC);
+	psFree(resolved);
+	return false;
+    }
+
+    // write the stats MDC to a file
+    // XXX why does this not call psMetadataConfigPrint?
+    fprintf(statsFile, "%s", statsMDC);
+    fclose(statsFile);
+
+    psFree(resolved);
+    psFree(statsMDC);
+    psFree(view);
+    psFree(stats);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModel.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModel.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModel.c	(revision 23321)
@@ -0,0 +1,65 @@
+/** @file psastroModel.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastroModel
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroStandAlone.h"
+
+int main (int argc, char **argv) {
+
+    pmConfig *config = NULL;
+
+    psTimerStart ("complete");
+
+    psastroErrorRegister();              // register our error codes/messages
+
+    // model inits are needed in pmSourceIO
+    // models defined in psphot/src/models are not available in psastro
+    pmModelClassInit ();
+
+    // load configuration information
+    config = psastroModelArguments (argc, argv);
+
+    // load identify the data sources
+    if (!psastroModelParseCamera (config)) {
+	psErrorStackPrint(stderr, "error setting up the camera\n");
+	exit (1);
+    }
+
+    // load the raw pixel data (from PSPHOT.SOURCES)
+    // select subset of stars for astrometry
+    if (!psastroModelDataLoad (config)) {
+	psErrorStackPrint(stderr, "error loading input data\n");
+	exit (1);
+    }
+
+    // run the full astrometry analysis (chip and/or mosaic)
+    if (!psastroModelAnalysis (config)) {
+	psErrorStackPrint(stderr, "failure in psastro model analysis\n");
+	exit (1);
+    }
+    
+    // run the full astrometry analysis (chip and/or mosaic)
+    if (!psastroModelAdjust (config)) {
+	psErrorStackPrint(stderr, "failure in psastro model adjust\n");
+	exit (1);
+    }
+    
+    // save the model
+    if (!psastroModelDataSave (config)) {
+	psErrorStackPrint(stderr, "error saving output data\n");
+	exit (1);
+    }
+
+    psLogMsg ("psastro", 3, "complete psastro run: %f sec\n", psTimerMark ("complete"));
+
+    // psastroCleanup (config);
+    exit (EXIT_SUCCESS);
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelAdjust.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelAdjust.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelAdjust.c	(revision 23321)
@@ -0,0 +1,257 @@
+/** @file psastroModelAdjust.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastroModel
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroStandAlone.h"
+# define NONLIN_TOL 0.001 ///< tolerance in pixels 
+# define DEBUG 0
+
+bool psastroModelAdjustBoresite (pmFPAfile *output, pmChip *refChip);
+
+bool psastroModelAdjust (pmConfig *config) {
+
+    bool status;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe");
+	return false;
+    }
+
+    // if we have not measured the boresite position, no adjustment is needed
+    bool fitBoresite = psMetadataLookupBool (&status, recipe, "PSASTRO.MODEL.FIT.BORESITE");
+    if (!status) psAbort ("Can't find recipe option PSASTRO.MODEL.FIT.BORESITE");
+
+    // as an alternative to fit the boresite from a rotation sequence, we can set the boresite
+    // relative to the reference chip coordinates
+    bool setBoresite = psMetadataLookupBool (&status, recipe, "PSASTRO.MODEL.SET.BORESITE");
+    if (!status) psAbort ("Can't find recipe option PSASTRO.MODEL.SET.BORESITE");
+
+    if (fitBoresite && setBoresite) {
+	psError(PS_ERR_IO, true, "invalid to choose both FIT.BORESITE and SET.BORESITE"); 
+	return false; 
+    } 
+	
+    pmFPAfile *output = psMetadataLookupPtr (&status, config->files, "PSASTRO.OUT.MODEL");
+    if (!status) psAbort ("Can't find output pmFPAfile PSASTRO.OUT.MODEL");
+    if (!output->fpa) psAbort ("no existing input fpa contains the reference chip");
+
+    // physical pixel scale in microns per pixel
+    char *refChipName = psMetadataLookupStr (&status, recipe, "PSASTRO.MODEL.REF.CHIP");
+    if (!refChipName) {
+	psError(PS_ERR_IO, true, "reference chip is missing from recipe"); 
+	return false; 
+    } 
+
+    // get reference chip from name
+    pmChip *refChip = pmConceptsChipFromName (output->fpa, refChipName);
+    if (!refChip) psAbort ("invalid chip name for reference");
+    if (!refChip->toFPA) psAbort ("invalid astrometry for reference chip");
+
+    // save the TPA region for tranformation inversions below
+    // psRegion *tpaRegion = pmAstromFPInTP (output->fpa);
+    psRegion *fpaRegion = pmAstromFPAExtent (output->fpa);
+
+    if (DEBUG) psastroDumpCorners ("corners.up.raw.dat", "corners.dn.raw.dat", output->fpa);
+
+    if (setBoresite) {
+	float boreXchip = psMetadataLookupF32 (&status, recipe, "PSASTRO.MODEL.BORESITE.X");
+	float boreYchip = psMetadataLookupF32 (&status, recipe, "PSASTRO.MODEL.BORESITE.Y");
+	psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.BORE.X0", PS_META_REPLACE, "boresite parameter", boreXchip); 
+	psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.BORE.Y0", PS_META_REPLACE, "boresite parameter", boreYchip); 
+    }
+
+    if (fitBoresite || setBoresite) {
+	psastroModelAdjustBoresite (output, refChip);
+    } else {
+	// FPA.BORE.X0,Y0 should be 0,0 in the focal plane, not the chip.  Ask for the
+	// coordinates which make refChip->toFPA(x,y) = (0,0)
+	psPlane *PT = psPlaneTransformGetCenter (refChip->toFPA, NONLIN_TOL);
+	psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.BORE.X0", PS_META_REPLACE, "boresite parameter", PT->x); 
+	psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.BORE.Y0", PS_META_REPLACE, "boresite parameter", PT->y); 
+	psFree (PT);
+    }
+
+    // rotate the chip-to-FPA transforms to have 0.0 posangle for refChip; 
+    // compensate by rotating fpa to TPA transform
+
+    // get the current posangle of the ref chip
+    float chipAngle = atan2 (refChip->toFPA->y->coeff[1][0], refChip->toFPA->x->coeff[1][0]);
+    fprintf (stderr, "chipAngle: %f\n", chipAngle*PS_DEG_RAD);
+    // psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.POSANGLE", PS_META_REPLACE, "boresite parameter", posangle);
+
+    // rotate the chip transforms
+    for (int i = 0; i < output->fpa->chips->n; i++) {
+	pmChip *chip = output->fpa->chips->data[i];
+	if (!chip->toFPA) continue;
+	// skip chips without astrometry
+
+	// save the region of this chip for the inversion below
+	psRegion *region = pmChipPixels (chip);
+
+	psPlaneTransform *toFPA = psPlaneTransformRotate (NULL, chip->toFPA, chipAngle);
+	psFree (chip->toFPA);
+	chip->toFPA = toFPA;
+
+	// invert the new fromFPA transform to get the new toFPA transform
+	psPlaneTransform *fromFPA = psPlaneTransformInvert(NULL, chip->toFPA, *region, 50);
+	psFree (chip->fromFPA);
+	chip->fromFPA = fromFPA;
+
+	psFree (region);
+
+	// save the transformation in the header
+	pmAstromWriteBilevelChip (chip->hdu->header, chip, NONLIN_TOL);
+    }
+
+    // get the current posangle of the fpa
+    float fpaAngle = atan2 (output->fpa->toTPA->y->coeff[1][0], output->fpa->toTPA->x->coeff[1][0]);
+    fprintf (stderr, "fpaAngle: %f\n", fpaAngle*PS_DEG_RAD);
+    // psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.POSANGLE", PS_META_REPLACE, "boresite parameter", posangle);
+
+    // remove the fpa rotation to generate a rotation-free model
+    psPlaneTransform *toTPA = psPlaneTransformRotate (NULL, output->fpa->toTPA, fpaAngle);
+    psFree (output->fpa->toTPA);
+    output->fpa->toTPA = toTPA;
+
+    psFree (output->fpa->fromTPA);
+    output->fpa->fromTPA = psPlaneTransformInvert(NULL, output->fpa->toTPA, *fpaRegion, 50);
+
+    // the model now describes the unrotated focal-plane
+    if (DEBUG) psastroDumpCorners ("corners.up.rot.dat", "corners.dn.rot.dat", output->fpa);
+
+    psMetadata *header = output->fpa->hdu->header;
+    pmAstromWriteBilevelMosaic (header, output->fpa, NONLIN_TOL);
+
+    psFree (fpaRegion);
+
+    return true;
+}
+
+bool psastroModelAdjustBoresite (pmFPAfile *output, pmChip *refChip) {
+
+    bool status;
+
+    psPlane  *boreCH  = psPlaneAlloc();
+    psPlane  *boreFP  = psPlaneAlloc();    
+    psPlane  *boreTP  = psPlaneAlloc();    
+    psSphere *boreSky = psSphereAlloc();    
+
+    // correct Xo,Yo to Lo,Mo using the ref chip toFPA
+    // ref chip position of the true boresite center
+    boreCH->x = psMetadataLookupF32 (&status, output->fpa->concepts, "FPA.BORE.X0"); 
+    boreCH->y = psMetadataLookupF32 (&status, output->fpa->concepts, "FPA.BORE.Y0"); 
+    psPlaneTransformApply (boreFP, refChip->toFPA, boreCH);
+
+    // adjust the reference pixel for all chips
+    for (int i = 0; i < output->fpa->chips->n; i++) {
+	pmChip *chip = output->fpa->chips->data[i];
+	if (!chip->fromFPA) continue;
+	// skip the chips without astrometry
+
+	// save the FPA region of this chip for the inversion below
+	psRegion *region = pmChipPixels (chip);
+
+	// the current toFPA returns boreFP->x,y for the boresite; subtract this from the transformations
+	chip->toFPA->x->coeff[0][0] -= boreFP->x;
+	chip->toFPA->y->coeff[0][0] -= boreFP->y;
+
+	// psPlaneTransform *toFPA = psPlaneTransformSetCenter (NULL, chip->toFPA, -boreFP->x, -boreFP->y);
+	// psFree (chip->toFPA);
+	// chip->toFPA = toFPA;
+
+	// invert the new fromFPA transform to get the new toFPA transform
+	// the region used here is the region covered by the chip in the FPA
+	psPlaneTransform *fromFPA = psPlaneTransformInvert(NULL, chip->toFPA, *region, 50); 
+	psFree (chip->fromFPA);
+	chip->fromFPA = fromFPA;
+
+	psFree (region);
+    }
+
+    if (DEBUG) psastroDumpCorners ("corners.up.shf.dat", "corners.dn.shf.dat", output->fpa);
+
+    // we have now adjusted the chips to use the correct boresite position as the center of the focal-plane system.
+    // we now need to reconstruct the TP to FP transformation, starting from stars projected about this new boresite position.
+
+    // find the R,D of the new boresite (boreFP -> 0,0; 0,0 -> -boreFP)
+    boreFP->x = -boreFP->x;
+    boreFP->y = -boreFP->y;
+    psPlaneTransformApply (boreTP, output->fpa->toTPA, boreFP);
+    psDeproject (boreSky, boreTP, output->fpa->toSky); // find the RA,DEC coord of the focal-plane coordinate
+
+    psProjection *newSky = psProjectionAlloc (boreSky->r, boreSky->d, output->fpa->toSky->Xs, output->fpa->toSky->Ys, output->fpa->toSky->type);
+
+    // generate a collection of points on the sky using the old toTPA transformation and toSky projection, projected with the newSky projection
+    // this is the FPA coordinate range covered by the FP: 
+    psRegion *fpaRegion = pmAstromFPAExtent (output->fpa);
+    float dx = (fpaRegion->x1 - fpaRegion->x0) / 50.0;
+    float dy = (fpaRegion->y1 - fpaRegion->y0) / 50.0;
+
+    psPlane fp, tp;
+    psSphere sky;
+
+    psVector *FPx = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *FPy = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *TPx = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *TPy = psVectorAllocEmpty (100, PS_TYPE_F32);
+
+    // XXX a test: boreFP->x,y, should transform to tp.x,y = 0,0
+    fp.x = boreFP->x;
+    fp.y = boreFP->y;
+    psPlaneTransformApply (&tp, output->fpa->toTPA, &fp);
+    psDeproject (&sky, &tp, output->fpa->toSky); // find the RA,DEC coord of the focal-plane coordinate
+    psProject (&tp, &sky, newSky); // find the RA,DEC coord of the focal-plane coordinate
+
+    int Npts = 0;
+    for (fp.x = fpaRegion->x0; fp.x <= fpaRegion->x1; fp.x += dx) {
+	for (fp.y = fpaRegion->y0; fp.y <= fpaRegion->y1; fp.y += dy) {
+	    psPlaneTransformApply (&tp, output->fpa->toTPA, &fp);
+	    psDeproject (&sky, &tp, output->fpa->toSky); // find the RA,DEC coord of the focal-plane coordinate
+	    psProject (&tp, &sky, newSky); // find the RA,DEC coord of the focal-plane coordinate
+
+	    // we are fitting points in the NEW FP system to points in the NEW TP system
+	    FPx->data.F32[Npts] = fp.x - boreFP->x;
+	    FPy->data.F32[Npts] = fp.y - boreFP->y;
+	    TPx->data.F32[Npts] = tp.x;
+	    TPy->data.F32[Npts] = tp.y;
+	    psVectorExtend (FPx, 100, 1);
+	    psVectorExtend (FPy, 100, 1);
+	    psVectorExtend (TPx, 100, 1);
+	    psVectorExtend (TPy, 100, 1);
+	    Npts ++;
+	}
+    }
+    psFree (fpaRegion);
+
+    // fit both up and down transformations to the same points
+    psVectorFitPolynomial2D (output->fpa->toTPA->x, NULL, 0, TPx, NULL, FPx, FPy);
+    psVectorFitPolynomial2D (output->fpa->toTPA->y, NULL, 0, TPy, NULL, FPx, FPy);
+    psVectorFitPolynomial2D (output->fpa->fromTPA->x, NULL, 0, FPx, NULL, TPx, TPy);
+    psVectorFitPolynomial2D (output->fpa->fromTPA->y, NULL, 0, FPy, NULL, TPx, TPy);
+
+    psFree (output->fpa->toSky);
+    output->fpa->toSky = newSky;
+
+    if (DEBUG) psastroDumpCorners ("corners.up.bore.dat", "corners.dn.bore.dat", output->fpa);
+
+    psFree (FPx);
+    psFree (FPy);
+    psFree (TPx);
+    psFree (TPy);
+
+    psFree (boreCH);
+    psFree (boreFP);
+    psFree (boreTP);
+    psFree (boreSky);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelAnalysis.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelAnalysis.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelAnalysis.c	(revision 23321)
@@ -0,0 +1,174 @@
+/** @file psastroModelAnalysis.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastroModel
+ *
+ *  @author IfA
+ *  @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroStandAlone.h"
+# define NONLIN_TOL 0.001
+
+bool psastroModelAnalysis (pmConfig *config) {
+
+    bool status;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe");
+	return false;
+    }
+
+    // reference chip for boresite parameters
+    char *refChip = psMetadataLookupStr (&status, recipe, "PSASTRO.MODEL.REF.CHIP");
+    if (!refChip) {
+	psError(PS_ERR_IO, true, "reference chip is missing from recipe"); 
+	return false; 
+    } 
+
+    pmFPAfile *output = psMetadataLookupPtr (&status, config->files, "PSASTRO.OUT.MODEL");
+    if (!status) psAbort ("Can't find output pmFPAfile PSASTRO.OUT.MODEL");
+
+    // measure the boresite position from a rotation sequence?
+    bool fitBoresite = psMetadataLookupBool (&status, recipe, "PSASTRO.MODEL.FIT.BORESITE");
+    if (!fitBoresite) {
+	psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.BORE.X0", PS_META_REPLACE, "boresite parameter", 0.0); 
+	psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.BORE.Y0", PS_META_REPLACE, "boresite parameter", 0.0); 
+	psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.BORE.RX", PS_META_REPLACE, "boresite parameter", 0.0); 
+	psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.BORE.RY", PS_META_REPLACE, "boresite parameter", 0.0); 
+	psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.BORE.T0", PS_META_REPLACE, "boresite parameter", 0.0); 
+	psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.BORE.P0", PS_META_REPLACE, "boresite parameter", 0.0); 
+	psMetadataAddStr (output->fpa->concepts, PS_LIST_TAIL, "FPA.REF.CHIP", PS_META_REPLACE, "boresite parameter", refChip);
+	return true; 
+    } 
+
+    char *outroot = psMetadataLookupStr (&status, config->arguments, "OUTPUT");
+    if (!status || !outroot) psAbort ("Can't find outroot on config->arguments");
+
+    /* model analysis:
+     *
+     * determine POS_ZERO via comparison of measured and reported posangles
+     * POS_ZERO = FPA.POSANGLE - posangle
+     *
+     * determine boresite model:
+     * X = Xo + R_X cos(FPA.POSANGLE - T_0) cos(P_0) + R_Y sin(FPA.POSANGLE - T_0) sin(P_0) 
+     * Y = Yo + R_Y sin(FPA.POSANGLE - T_0) cos(P_0) - R_X cos(FPA.POSANGLE - T_0) sin(P_0) 
+     * position of reported boresite in reference chip pixels
+     * Xo, Yo : true coordinate of boresite (rotator center) in reference chip pixels
+     * R_X, R_Y : amplitude of boresite offset
+     * T_0 : reference angle for rotator
+     * P_0 : orientation of boresite ellipse
+     *
+     */
+
+    // select the input pmFPAfile pointers
+    psMetadataItem *item = psMetadataLookup (config->files, "PSASTRO.WCS");
+    if (item == NULL) psAbort("missing PSASTRO.WCS entries in config->files");
+    if (item->type != PS_DATA_METADATA_MULTI) psAbort("unexpected type for PSASTRO.WCS");
+    psArray *files = psListToArray (item->data.list);
+
+    // data storage vectors for measurements
+    psVector *posZero  = psVectorAlloc (files->n, PS_TYPE_F32);
+    psVector *Po       = psVectorAlloc (files->n, PS_TYPE_F32);
+    psVector *Xo       = psVectorAlloc (files->n, PS_TYPE_F32);
+    psVector *Yo       = psVectorAlloc (files->n, PS_TYPE_F32);
+
+    // counter for accepted measured values
+    int n = 0;
+
+    // Re-select the output fpa.  The output fpa needs to have valid astrometry for the
+    // refChip.  output->fpa is a copy of the pointer to one of the input->fpa, but the choice
+    // is arbitrary.  select a new one that has an existing ref chip
+    psFree (output->fpa);
+    output->fpa = NULL;
+
+    char filename[256];
+    snprintf (filename, 256, "%s.bore", outroot);
+    FILE *outfile = fopen (filename, "w");
+    if (!outfile) {
+        psError(PSASTRO_ERR_ARGUMENTS, true, "cannot open %s for output", filename);
+	return false;
+    }
+
+    float posBoundary = 0.0;
+
+    // extract the relevant measured and reported values from the reference chip
+    for (int i = 0; i < files->n; i++) {
+	psMetadataItem *file = files->data[i];
+	pmFPAfile *input = file->data.V;
+
+	// reported rotator position angle
+	double POSANGLE = psMetadataLookupF64 (&status, input->fpa->concepts, "FPA.POSANGLE"); 
+	if (!status) psAbort ("missing FPA.POSANGLE");
+
+    	// get reference chip from name
+	pmChip *chip = pmConceptsChipFromName (input->fpa, refChip);
+	if (!chip) psAbort ("invalid chip name for reference");
+
+	if (!chip->toFPA) continue;
+
+	if (!output->fpa) {
+	    // this one matches
+	    output->fpa = psMemIncrRefCounter(input->fpa);
+	}
+
+	// we have two measurements of the posangle (may be parity flipped to different quadrants)
+	// atan2 returns values in the range 0-2pi
+	// all posZero values should be clustered in some region, but we need to flip over the 0,360 boundary correctly.
+	// push all to one side or the other
+	float chipAngle = PM_DEG_RAD * atan2 (chip->toFPA->y->coeff[1][0], chip->toFPA->x->coeff[1][0]);
+	float fpaAngle = PM_DEG_RAD * atan2 (input->fpa->toTPA->y->coeff[1][0], input->fpa->toTPA->x->coeff[1][0]);
+
+	posZero->data.F32[n] = POSANGLE - chipAngle - fpaAngle;
+	if (n == 0) {
+	    posBoundary = posZero->data.F32[n] + 180.0;
+	} else {
+	    while (posZero->data.F32[n] > posBoundary) posZero->data.F32[n] -= 360.0;
+	    while (posZero->data.F32[n] < posBoundary - 360.0) posZero->data.F32[n] += 360.0;
+	}
+
+	Po->data.F32[n] = POSANGLE * PM_RAD_DEG; // reported position angle
+	float xc = chip->fromFPA->x->coeff[0][0]; // reported boresite x position in ref chip coordinates
+	float yc = chip->fromFPA->y->coeff[0][0]; // reported boresite y position in ref chip coordinates
+	// XXX this can also be derived from toFPA via GetCenter....
+	
+	psPlane *PT = psPlaneTransformGetCenter (chip->toFPA, NONLIN_TOL);
+	Xo->data.F32[n] = PT->x; // reported boresite x position in ref chip coordinates
+	Yo->data.F32[n] = PT->y; // reported boresite y position in ref chip coordinates
+	psFree (PT);
+
+	fprintf (outfile, "%d : %f %f : %f = %f - %f - %f | %f %f\n", i, Xo->data.F32[n], Yo->data.F32[n], posZero->data.F32[n], POSANGLE, chipAngle, fpaAngle, xc, yc);
+	n ++;
+    }
+      
+    Xo->n = n;
+    Yo->n = n;
+    Po->n = n;
+    posZero->n = n;
+
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    psVectorStats (stats, posZero, NULL, NULL, 0);
+
+    fprintf (outfile, "# pos zero %f +/- %f\n", stats->sampleMedian, stats->sampleStdev);
+    psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.POS_ZERO", PS_META_REPLACE, "offset between obs and meas posangle", stats->sampleMedian); 
+    fclose (outfile);
+
+    psVector *params = psastroModelFitBoresite (Xo, Yo, Po, outroot);
+    if (params->n != 6) psAbort ("error");
+
+
+    psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.BORE.X0", PS_META_REPLACE, "boresite parameter", params->data.F32[PAR_X0]); 
+    psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.BORE.Y0", PS_META_REPLACE, "boresite parameter", params->data.F32[PAR_Y0]); 
+    psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.BORE.RX", PS_META_REPLACE, "boresite parameter", params->data.F32[PAR_RX]); 
+    psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.BORE.RY", PS_META_REPLACE, "boresite parameter", params->data.F32[PAR_RY]); 
+    psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.BORE.T0", PS_META_REPLACE, "boresite parameter", params->data.F32[PAR_T0]); 
+    psMetadataAddF32 (output->fpa->concepts, PS_LIST_TAIL, "FPA.BORE.P0", PS_META_REPLACE, "boresite parameter", params->data.F32[PAR_P0]); 
+    psMetadataAddStr (output->fpa->concepts, PS_LIST_TAIL, "FPA.REF.CHIP", PS_META_REPLACE, "boresite parameter", refChip);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelArguments.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelArguments.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelArguments.c	(revision 23321)
@@ -0,0 +1,75 @@
+/** @file psastroModelArguments.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastroModel
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroStandAlone.h"
+
+static char *usage = "USAGE: psastroModel [-output root] (cmffiles)";
+
+pmConfig *psastroModelArguments (int argc, char **argv) {
+
+    int N;
+
+    if (argc == 1) {
+        psError(PSASTRO_ERR_ARGUMENTS, true, "No arguments supplied");
+        psErrorStackPrint(stderr, "%s", usage);
+	exit (1);
+    }
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, PSASTRO_RECIPE);
+    if (config == NULL) {
+        psError(PSASTRO_ERR_CONFIG, false, "Can't read site configuration");
+        psErrorStackPrint(stderr, "%s", usage);
+	exit (1);
+    }
+
+    // save the following additional recipe values based on command-line options
+    // these options override the PSASTRO recipe values loaded from recipe files
+
+    // XXX no options yet defined
+    // psMetadata *options = pmConfigRecipeOptions (config, PSASTRO_RECIPE);
+
+    // define the output filename
+    if ((N = psArgumentGet (argc, argv, "-output"))) {
+        psArgumentRemove (N, &argc, argv);
+	psMetadataAddStr (config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    } else {
+        psError(PSASTRO_ERR_ARGUMENTS, true, "Missing -output (root)");
+        psErrorStackPrint(stderr, "%s", usage);
+	exit (1);
+    }
+
+    // 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_META_REPLACE, "", argv[N]);
+        psArgumentRemove (N, &argc, argv);
+    }
+    
+    if (argc < 1) {
+        psError(PSASTRO_ERR_ARGUMENTS, true, "Incorrect arguments supplied");
+        psErrorStackPrint(stderr, "exit");
+	exit (1);
+    }
+
+    // each additional word is a file; create names INPUT.%d for them
+    for (int i = 0; i < argc - 1; i++) {
+	char name[16];
+	snprintf (name, 16, "INPUT.%d", i);
+	psArray *array = psArrayAlloc(1);
+	array->data[0] = psStringCopy (argv[i+1]);
+	psMetadataAddPtr(config->arguments, PS_LIST_TAIL, name,  PS_DATA_ARRAY, "", array);
+    }	
+    psMetadataAddS32(config->arguments, PS_LIST_TAIL, "INPUT.N", 0, "", argc - 1);
+    return (config);
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelBoresite.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelBoresite.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelBoresite.c	(revision 23321)
@@ -0,0 +1,66 @@
+/** @file psastroModelBoresite.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastroModel
+ *
+ *  @author IfA
+ *  @version $Revision: 1.4 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroStandAlone.h"
+
+/**
+ * the full chisq is built of two associated sums over coordinates:
+ * chisq = sum ((X_obs - X_fit(t))^2 + (Y_obs - Y_fit(t))^2)
+ * we use split this into a 2x long vector and use coord[1] to distinguish the X and Y terms:
+ * coord[0] = measured X or measured Y
+ * coord[1] =          0 or          1
+ */
+psF32 psastroModelBoresite (psVector *deriv, const psVector *params, const psVector *coord) {
+
+    psF32 *PAR = params->data.F32;
+
+    float csphi = cos(PAR[PAR_P0]);
+    float snphi = sin(PAR[PAR_P0]);
+
+    float dtheta = coord->data.F32[0] - PAR[PAR_T0];
+    float cstht = cos(dtheta);
+    float sntht = sin(dtheta);
+
+    // value is X
+    if (coord->data.F32[1] == 0) {
+
+	float value = PAR[PAR_X0] + PAR[PAR_RX]*cstht*csphi + PAR[PAR_RY]*sntht*snphi;
+
+	if (deriv) {
+	    psF32 *dPAR = deriv->data.F32;
+	    dPAR[PAR_X0] = 1.0;
+	    dPAR[PAR_Y0] = 0.0;
+	    dPAR[PAR_RX] = +cstht*csphi;
+	    dPAR[PAR_RY] = +sntht*snphi;
+	    dPAR[PAR_P0] = -PAR[PAR_RX]*cstht*snphi + PAR[PAR_RY]*sntht*csphi;
+	    dPAR[PAR_T0] =  PAR[PAR_RX]*sntht*csphi - PAR[PAR_RY]*cstht*snphi;
+	}
+	return (value);
+    }  
+
+    // value is Y
+    if (coord->data.F32[1] == 1) {
+	float value = PAR[PAR_Y0] + PAR[PAR_RY]*sntht*csphi - PAR[PAR_RX]*cstht*snphi;
+
+	if (deriv) {
+	    psF32 *dPAR = deriv->data.F32;
+	    dPAR[PAR_X0]  = 0.0;
+	    dPAR[PAR_Y0]  = 1.0;
+	    dPAR[PAR_RX]  = -cstht*snphi;
+	    dPAR[PAR_RY]  = +sntht*csphi;
+	    dPAR[PAR_P0]  = -PAR[PAR_RY]*sntht*snphi - PAR[PAR_RX]*cstht*csphi;
+	    dPAR[PAR_T0]  = -PAR[PAR_RY]*cstht*csphi - PAR[PAR_RX]*sntht*snphi;
+	}
+	return (value);
+    }  
+    psAbort ("programming error: invalid coordinate");
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelDataLoad.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelDataLoad.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelDataLoad.c	(revision 23321)
@@ -0,0 +1,113 @@
+/** @file psastroModelDataLoad.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastroModel
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroStandAlone.h"
+
+# define ESCAPE { \
+  psError(PS_ERR_UNKNOWN, false, "Failure in psastroModelDataLoad"); \
+  psFree (view); \
+  return false; \
+}
+  
+/**
+ * this loop loads the header data from the input files, using the output 
+ * pmFPAfile to guide the chip selection and related issues
+ * all of the different astrometry analysis modes use the same data load loop
+ */
+bool psastroModelDataLoad (pmConfig *config) {
+
+    bool status;
+    pmChip *chip;
+
+    psTimerStart ("psastro");
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe!\n");
+	return false;
+    }
+
+    // physical pixel scale in microns per pixel
+    double pixelScale = psMetadataLookupF32 (&status, recipe, "PSASTRO.PIXEL.SCALE");
+    if (!status) {
+	psError(PS_ERR_IO, true, "Failed to lookup pixel scale"); 
+	return false; 
+    } 
+
+    // select the input data sources
+    pmFPAfile *output = psMetadataLookupPtr (NULL, config->files, "PSASTRO.OUT.MODEL");
+    if (!output) psAbort ("PSASTRO.OUT.MODEL not listed in config->files");
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+
+    while ((chip = pmFPAviewNextChip (view, output->fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process) { continue; }
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+    }
+    psLogMsg ("psastro", 3, "load headers : %f sec\n", psTimerMark ("psastro"));
+
+    // we should have a number of different files stored in config->files as PSASTRO.WCS
+    psMetadataItem *item = psMetadataLookup (config->files, "PSASTRO.WCS");
+    if (item == NULL) psAbort("missing PSASTRO.WCS entries in config->files");
+    if (item->type != PS_DATA_METADATA_MULTI) psAbort("unexpected type for PSASTRO.WCS");
+    psArray *files = psListToArray (item->data.list);
+
+    // convert the headers for the input file into fpa astrometry terms
+    for (int i = 0; i < files->n; i++) {
+	psMetadataItem *file = files->data[i];
+	pmFPAfile *input = file->data.V;
+
+	pmFPAviewReset (view);
+
+	// check PHU header to see if we are using mosaic-level or per-chip astrometry
+	bool bilevelAstrometry = false;
+	pmHDU *phu = pmFPAviewThisPHU (view, input->fpa);
+	if (phu) {
+	    char *ctype = psMetadataLookupStr (NULL, phu->header, "CTYPE1");
+	    if (ctype) bilevelAstrometry = !strcmp (&ctype[4], "-DIS");
+	}
+	if (bilevelAstrometry) {
+	    pmAstromReadBilevelMosaic (input->fpa, phu->header);
+	} 
+
+	while ((chip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+	    psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+	    if (!chip->process || !chip->file_exists || !chip->data_exists) { continue; }
+
+	    // read WCS data from the corresponding header
+	    pmHDU *hdu = pmFPAviewThisHDU (view, input->fpa);
+	    int nAstro = psMetadataLookupS32 (&status, hdu->header, "NASTRO");
+	    if (!nAstro) continue;
+
+	    if (bilevelAstrometry) {
+		if (!pmAstromReadBilevelChip (chip, hdu->header)) {
+		    psWarning("Could not get WCS information from header for chip %d, skipping", view->chip); 
+		    continue;
+		} 
+	    } else {
+		if (!pmAstromReadWCS (input->fpa, chip, hdu->header, pixelScale)) {
+		    psWarning("Could not get WCS information from header for chip %d, skipping", view->chip); 
+		    continue;
+		} 
+	    }
+	}
+    }
+    psLogMsg ("psastro", 3, "convert wcs terms to internal format : %f sec\n", psTimerMark ("psastro"));
+
+    psFree (view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelDataSave.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelDataSave.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelDataSave.c	(revision 23321)
@@ -0,0 +1,52 @@
+/** @file psastroModelDataSave.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastroModel
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroStandAlone.h"
+# define NONLIN_TOL 0.001 ///< tolerance in pixels 
+
+# define ESCAPE { \
+  psError(PS_ERR_UNKNOWN, false, "Failure in psastroModelDataSave"); \
+  psFree (view); \
+  return false; \
+}
+  
+bool psastroModelDataSave (pmConfig *config) {
+
+    bool status;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe");
+	return false;
+    }
+
+    pmFPAfile *output = psMetadataLookupPtr (&status, config->files, "PSASTRO.OUT.MODEL");
+    if (!status) psAbort ("Can't find output pmFPAfile PSASTRO.OUT.MODEL");
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmChip *chip = NULL;
+
+    while ((chip = pmFPAviewNextChip (view, output->fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process) { continue; }
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE;
+    }
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_AFTER)) ESCAPE;
+
+    psLogMsg ("psastro", 3, "save headers : %f sec\n", psTimerMark ("psastro"));
+
+    return true;
+}
+
+
+
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelFit.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelFit.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelFit.c	(revision 23321)
@@ -0,0 +1,49 @@
+/** @file psastroModelFit.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastroModel
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroStandAlone.h"
+
+int main (int argc, char **argv) {
+
+    psTimerStart ("complete");
+
+    if (argc != 3) {
+	fprintf (stderr, "USAGE: psastroModelFit (input) (output)\n");
+	exit (1);
+    }
+
+    FILE *f = fopen (argv[1], "r");
+    if (f == NULL) {
+	fprintf (stderr, "problem opening data file %s\n", argv[1]);
+	exit (2);
+    }
+
+    char name[1024];
+    psVector *Xo = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *Yo = psVectorAllocEmpty (100, PS_TYPE_F32);
+    psVector *Po = psVectorAllocEmpty (100, PS_TYPE_F32);
+
+    float x, y, p;
+
+    while (fscanf (f, "%s %f %f %f", name, &x, &y, &p) != EOF) {
+	psVectorAppend (Xo, x);
+	psVectorAppend (Yo, y);
+	psVectorAppend (Po, p*PS_RAD_DEG);
+    }	
+
+    // psTraceSetLevel("psLib.math", 5);
+    psastroModelFitBoresite (Xo, Yo, Po, argv[2]);
+
+    psLogMsg ("psastro", 3, "complete psastroModelFit run: %f sec\n", psTimerMark ("complete"));
+
+    exit (EXIT_SUCCESS);
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelFitBoresite.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelFitBoresite.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelFitBoresite.c	(revision 23321)
@@ -0,0 +1,107 @@
+/** @file psastroModelFitBoresite.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastroModel
+ *
+ *  @author IfA
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroStandAlone.h"
+
+/** 
+ * we now have a set of observed L,M values.  fit these to the boresite model
+ */
+psVector *psastroModelFitBoresite (psVector *Xo, psVector *Yo, psVector *Po, char *outroot) {
+
+    assert (Xo->n > 2);
+    assert (Xo->n == Yo->n);
+    assert (Xo->n == Po->n);
+
+    // arrays to hold the data to be fitted
+    psArray *x = psArrayAlloc(2*Xo->n);
+    psVector *y = psVectorAlloc(2*Xo->n, PS_TYPE_F32);
+
+    int n = 0;
+    for (int i = 0; i < Xo->n; i++) {
+
+	psVector *coord = NULL;
+
+	// X coordinate value
+	coord = psVectorAlloc (2, PS_TYPE_F32);
+	coord->data.F32[1] = 0.0;
+	coord->data.F32[0] = Po->data.F32[i];
+	x->data[n] = coord;
+	y->data.F32[n] = Xo->data.F32[i];
+	n++;
+	
+	// Y coordinate value
+	coord = psVectorAlloc (2, PS_TYPE_F32);
+	coord->data.F32[1] = 1.0;
+	coord->data.F32[0] = Po->data.F32[i];
+	x->data[n] = coord;
+	y->data.F32[n] = Yo->data.F32[i];
+	n++;
+    }	
+    assert (x->n == n);
+    assert (y->n == n);
+
+    psVector *params = psVectorAlloc (6, PS_TYPE_F32);
+    
+    // create the minimization constraints
+    psMinConstraint *constraint = psMinConstraintAlloc();
+
+    // XXX for now, no parameter masks, skip checkLimits
+    // constraint->checkLimits = psastroModelBoresiteLimits;
+
+    // make an initial guess:
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEAN | PS_STAT_MAX | PS_STAT_MIN);
+
+    // center (Xo) = mean(Xo), RX = range / 2
+    psVectorStats (stats, Xo, NULL, NULL, 0);
+    params->data.F32[PAR_X0] = stats->sampleMean;
+    params->data.F32[PAR_RX] = (stats->max - stats->min) / 2.0;
+
+    // center (Yo) = mean(Yo), RY = range / 2
+    psVectorStats (stats, Yo, NULL, NULL, 0);
+    params->data.F32[PAR_Y0] = stats->sampleMean;
+    params->data.F32[PAR_RY] = (stats->max - stats->min) / 2.0;
+
+    params->data.F32[PAR_P0] = 0.0;
+    params->data.F32[PAR_T0] = 0.0;
+
+    psMinimization *myMin = psMinimizationAlloc (25, 0.001);
+    psImage *covar = psImageAlloc (params->n, params->n, PS_TYPE_F32);
+    
+    // fprintf (stderr, "guess values:\n");
+    // fprintf (stderr, "Xo:  %f\n", params->data.F32[PAR_X0]);
+    // fprintf (stderr, "Yo:  %f\n", params->data.F32[PAR_Y0]);
+    // fprintf (stderr, "RX:  %f\n", params->data.F32[PAR_RX]);
+    // fprintf (stderr, "RY:  %f\n", params->data.F32[PAR_RY]);
+    // fprintf (stderr, "P0:  %f\n", params->data.F32[PAR_P0]);
+    // fprintf (stderr, "T0:  %f\n", params->data.F32[PAR_T0]);
+
+    // XXX skip the weights for now
+    psMinimizeLMChi2(myMin, covar, params, constraint, x, y, NULL, psastroModelBoresite);
+
+    char filename[256];
+    snprintf (filename, 256, "%s.pars", outroot);
+    FILE *outfile = fopen (filename, "w");
+    if (!outfile) {
+        psAbort("cannot open %s for output", filename);
+    }
+
+    fprintf (outfile, "# fitted values:\n");
+    fprintf (outfile, "Xo:  %f\n", params->data.F32[PAR_X0]);
+    fprintf (outfile, "Yo:  %f\n", params->data.F32[PAR_Y0]);
+    fprintf (outfile, "RX:  %f\n", params->data.F32[PAR_RX]);
+    fprintf (outfile, "RY:  %f\n", params->data.F32[PAR_RY]);
+    fprintf (outfile, "P0:  %f\n", params->data.F32[PAR_P0]);
+    fprintf (outfile, "T0:  %f\n", params->data.F32[PAR_T0]);
+    fclose (outfile);
+
+    return params;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelParseCamera.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelParseCamera.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroModelParseCamera.c	(revision 23321)
@@ -0,0 +1,56 @@
+/** @file psastroModelParseCamera.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastroModel
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+bool psastroModelParseCamera (pmConfig *config) {
+
+    bool status = false;
+    pmFPAfile *input = NULL;
+
+    int nInput = psMetadataLookupS32 (&status, config->arguments, "INPUT.N");
+    if (!status) psAbort ("missing INPUT.N in config->arguments");
+
+    // find and load the input files (this determines the camera and builds the fpa structures,
+    // but does not load the data)
+    for (int i = 0; i < nInput; i++) {
+	char name[16];
+	snprintf (name, 16, "INPUT.%d", i);
+	input = pmFPAfileDefineFromArgs (&status, config, "PSASTRO.WCS", name);
+    }
+	
+    // set up an output fpa structure & file based on the last input file
+    pmFPAfile *output = pmFPAfileDefineOutput (config, input->fpa, "PSASTRO.OUT.MODEL");
+    if (!output) {
+	psError(PSASTRO_ERR_CONFIG, false, "Failed to build FPA for PSASTRO.OUT.MODEL from input");
+	return false;
+    }
+    output->save = true;
+
+    // Chip selection: turn on only the chips specified (option is not required)
+    char *chipLine = psMetadataLookupStr(&status, config->arguments, "CHIP_SELECTIONS"); 
+    psArray *chips = psStringSplitArray (chipLine, ",", false);
+    if (chips->n > 0) {
+	pmFPASelectChip (output->fpa, -1, true); // deselect all chips
+	for (int i = 0; i < chips->n; i++) {
+	    int chipNum = atoi(chips->data[i]);
+	    if (! pmFPASelectChip(output->fpa, chipNum, false)) {
+		psError(PSASTRO_ERR_CONFIG, true, "Chip number %d doesn't exist in camera.\n", chipNum);
+		return false;
+	    }
+        }
+    }
+    psFree (chips);
+
+    psTrace("psastro", 1, "Done with psastroModelParseCamera...\n");
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicAstrom.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicAstrom.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicAstrom.c	(revision 23321)
@@ -0,0 +1,162 @@
+/** @file psastroMosaicAstrom.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.30 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+# define NONLIN_TOL 0.001 /* tolerance in pixels */
+
+bool psastroMosaicFit (pmFPA *fpa, psMetadata *recipe, const char *rootname, int pass);
+
+// XXX require this fpa to have multiple chip extensions and a PHU?
+bool psastroMosaicAstrom (pmConfig *config) {
+
+    bool status;
+    char filename[256];
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+        psError(PSASTRO_ERR_CONFIG, false, "Can't find PSASTRO recipe!\n");
+        return false;
+    }
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+        psError(PSASTRO_ERR_CONFIG, false, "Can't find input data!\n");
+        return false;
+    }
+
+    pmFPA *fpa = input->fpa;
+
+    // before we do object matches, we need to (optionally) fix failed chips.  We compare chips with
+    // the supplied mosaic model.  Adjust significant outliers to match model.
+    # if (0)
+    if (!psastroFixChips (config, recipe)) {
+        psError(PSASTRO_ERR_UNKNOWN, false, "failed to align problematic chips");
+        return false;
+    }
+    if (!psastroFixChipsTest (config, recipe)) {
+        psError(PSASTRO_ERR_UNKNOWN, false, "failed to align problematic chips");
+        return false;
+    }
+    # endif
+
+    char *outroot = psMetadataLookupStr (&status, config->arguments, "OUTPUT");
+    if (!status || !outroot) psAbort ("Can't find outroot on config->arguments");
+
+    if (!psastroMosaicFit (fpa, recipe, outroot, 0)) return false;
+    if (!psastroMosaicFit (fpa, recipe, outroot, 1)) return false;
+    if (!psastroMosaicFit (fpa, recipe, outroot, 2)) return false;
+    if (!psastroMosaicFit (fpa, recipe, outroot, 3)) return false;
+
+    // now fit the chips under the common distortion with higher-order terms
+    // first, re-perform the match with a slightly tighter circle
+    if (!psastroMosaicSetMatch (fpa, recipe, 4)) {
+        psError(PSASTRO_ERR_UNKNOWN, false, "failed to match raw and ref stars for mosaic (4th pass)");
+        return false;
+    }
+    if (!psastroMosaicChipAstrom (fpa, recipe, 4)) {
+        psError(PSASTRO_ERR_UNKNOWN, false, "failed to measure chip astrometry in mosaic mode (4th pass)");
+        return false;
+    }
+    if (psTraceGetLevel("psastro.dump") > 0) {
+        snprintf (filename, 256, "%s.10.dat", outroot);
+        psastroDumpMatches (fpa, filename);
+    }
+
+    // save WCS and analysis metadata in update header.
+    // (pull or create local view to entry on readout->analysis)
+    psMetadata *updates = psMetadataLookupMetadata (&status, fpa->analysis, "PSASTRO.HEADER");
+    if (!updates) {
+        updates = psMetadataAlloc ();
+        psMetadataAddMetadata (fpa->analysis, PS_LIST_TAIL, "PSASTRO.HEADER",  PS_META_REPLACE, "psastro header stats", updates);
+        psFree (updates);
+    }
+    if (!pmAstromWriteBilevelMosaic (updates, fpa, NONLIN_TOL)) {
+        psAbort ("failed to save header terms");
+    }
+
+    // write the elapsed time here; this will be updated in psastroMosaicAstrometry, if called
+    psMetadataAddF32 (updates, PS_LIST_TAIL, "DT_ASTR", PS_META_REPLACE, "elapsed psastro time", psTimerMark ("psastroAnalysis"));
+
+    // update the headers based on the results
+    // XXX need to add global summary statistics
+    // psastroMosaicHeaders (config);
+
+    return true;
+}
+
+/* coordinate frame hierachy
+ * pixels (on a given readout)
+ * cell
+ * chip
+ * FP (focal plane)
+ * TP (tangent plane)
+ * sky (ra, dec)
+ */
+
+// 1: match 4,5
+// 2: match 6,7
+// 3: match 8,9
+bool psastroMosaicFit (pmFPA *fpa, psMetadata *recipe, const char *rootname, int pass) {
+
+    char filename[256];
+
+    // given the existing per-chip astrometry, determine matches between raw and ref stars
+    // is this needed? yes, if we didn't do SingleChip astrometry first
+    if (!psastroMosaicSetMatch (fpa, recipe, pass)) {
+        psError(PSASTRO_ERR_UNKNOWN, false, "failed to match raw and ref stars for mosaic (pass %d)", pass);
+        return false;
+    }
+
+    if ((pass == 0) && (psTraceGetLevel("psastro.dump.psastroMosaicAstrom") > 1)) {
+        snprintf (filename, 256, "%s.0.dat", rootname);
+        psastroDumpMatches (fpa, filename);
+    }
+
+    // fitted chips will follow the local plate-scale, hiding the distortion
+    // modify the chip->toFPA scaling to match knowledge about pixel scale,
+    // then recalculate raw and ref positions
+    if (!psastroMosaicCommonScale (fpa, recipe)) {
+        psError(PSASTRO_ERR_UNKNOWN, false, "failed to set a common scale for the chips (pass %d)", pass);
+        return false;
+    }
+
+    if ((pass == 0) && (psTraceGetLevel("psastro.dump.psastroMosaicAstrom") > 1)) {
+        snprintf (filename, 256, "%s.1.dat", rootname);
+        psastroDumpMatches (fpa, filename);
+    }
+
+    // fit the distortion by fitting its gradient
+    // apply the new distortion terms up and down
+    // refit the per-chip terms with linear fits only
+    if (!psastroMosaicDistortion (fpa, recipe, pass)) {
+        psError(PSASTRO_ERR_UNKNOWN, false, "failed to measure mosaic gradients (pass %d)", pass);
+        return false;
+    }
+
+    snprintf (filename, 256, "%s.%d.dat", rootname, 2*pass + 2);
+    if (psTraceGetLevel("psastro.dump.psastroMosaicAstrom") > 1) { psastroDumpMatches (fpa, filename); }
+
+    // measure the astrometry for the chips under the distortion term
+    if (!psastroMosaicChipAstrom (fpa, recipe, pass)) {
+        psError(PSASTRO_ERR_UNKNOWN, false, "failed to measure chip astrometry in mosaic mode (pass %d)", pass);
+        return false;
+    }
+
+    if (psTraceGetLevel("psastro.dump.psastroMosaicAstrom") > 1) {
+        snprintf (filename, 256, "%s.%d.dat", rootname, 2*pass + 3);
+        psastroDumpMatches (fpa, filename);
+    }
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicChipAstrom.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicChipAstrom.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicChipAstrom.c	(revision 23321)
@@ -0,0 +1,69 @@
+/** @file psastroMosaiciChipAstrom.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.15 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+# define NONLIN_TOL 0.001 ///< tolerance in pixels
+
+bool psastroMosaicChipAstrom (pmFPA *fpa, psMetadata *recipe, int iteration) {
+
+    bool status;
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+        if (!chip->toFPA) { continue; }
+
+        while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 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
+            // XXX there can only be one readout per chip, right?
+            while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+                if (! readout->data_exists) { continue; }
+
+                // save WCS and analysis metadata in update header
+		// (pull or create local view to entry on readout->analysis)
+		psMetadata *updates = psMetadataLookupMetadata (&status, readout->analysis, "PSASTRO.HEADER");
+		if (!updates) {
+		    updates = psMetadataAlloc ();
+		    psMetadataAddMetadata (readout->analysis, PS_LIST_TAIL, "PSASTRO.HEADER",  PS_META_REPLACE, "psastro header stats", updates);
+		    psFree (updates);
+		}
+
+                if (!psastroMosaicOneChip (chip, readout, recipe, updates, iteration)) {
+                    readout->data_exists = false;
+                    psError(PS_ERR_UNKNOWN, false, "failed to find a solution for %d,%d,%d\n", view->chip, view->cell, view->readout);
+		    psErrorStackPrint(stderr, "failure for one chip\n");
+		    psErrorClear();
+		    continue;
+                }
+
+                // create the header keywords to descripe the results
+                if (!pmAstromWriteBilevelChip (updates, chip, NONLIN_TOL)) {
+                    readout->data_exists = false;
+                    psError(PS_ERR_UNKNOWN, false, "invalid solution for %d,%d,%d\n", view->chip, view->cell, view->readout);
+		    psErrorStackPrint(stderr, "failure for one chip\n");
+		    psErrorClear();
+		    continue;
+                }
+            }
+        }
+    }
+    psFree (view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicCorrectDistortion.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicCorrectDistortion.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicCorrectDistortion.c	(revision 23321)
@@ -0,0 +1,93 @@
+/** @file psastroMosaicCorrectDistortion.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+bool psastroMosaicCorrectDistortion (pmFPA *fpa, psPlaneTransform *TPtoFP) {
+
+    // invert the linear TPtoFP transform
+    psPlaneTransform *FPtoTP = p_psPlaneTransformLinearInvert (TPtoFP);
+    if (FPtoTP == NULL) {
+        psError (PS_ERR_UNKNOWN, false, "failed to invert TPtoFP\n");
+        return false;
+    }
+
+    // store the new coeffs in a new structure
+    psPlaneTransform *toTPAnew = psPlaneTransformAlloc(fpa->toTPA->x->nX, fpa->toTPA->x->nY);
+
+    // set the new coeffs, or set the mask
+    for (int i = 0; i <= toTPAnew->y->nX; i++) {
+        for (int j = 0; j <= toTPAnew->y->nY; j++) {
+
+	    // init the coeffs
+	    toTPAnew->x->coeff[i][j] = 0.0;
+	    toTPAnew->y->coeff[i][j] = 0.0;
+
+	    // if both are masked, mask the outpu
+            if ((fpa->toTPA->x->coeffMask[i][j] & PS_POLY_MASK_SET) && (fpa->toTPA->x->coeffMask[i][j] & PS_POLY_MASK_SET)) {
+		toTPAnew->x->coeffMask[i][j] = PS_POLY_MASK_SET;
+		toTPAnew->y->coeffMask[i][j] = PS_POLY_MASK_SET;
+		continue;
+	    } 
+
+	    // set the X terms
+	    toTPAnew->x->coeff[i][j] += (fpa->toTPA->x->coeffMask[i][j] & PS_POLY_MASK_SET) ? 0.0 : FPtoTP->x->coeff[1][0] * fpa->toTPA->x->coeff[i][j];
+	    toTPAnew->x->coeff[i][j] += (fpa->toTPA->y->coeffMask[i][j] & PS_POLY_MASK_SET) ? 0.0 : FPtoTP->x->coeff[0][1] * fpa->toTPA->y->coeff[i][j];
+
+	    // set the Y terms
+	    toTPAnew->y->coeff[i][j] += (fpa->toTPA->x->coeffMask[i][j] & PS_POLY_MASK_SET) ? 0.0 : FPtoTP->y->coeff[1][0] * fpa->toTPA->x->coeff[i][j];
+	    toTPAnew->y->coeff[i][j] += (fpa->toTPA->y->coeffMask[i][j] & PS_POLY_MASK_SET) ? 0.0 : FPtoTP->y->coeff[0][1] * fpa->toTPA->y->coeff[i][j];
+        }
+    }
+
+    // adjust the 0,0 terms:
+    toTPAnew->x->coeff[0][0] += FPtoTP->x->coeff[0][0];
+    toTPAnew->y->coeff[0][0] += FPtoTP->y->coeff[0][0];
+
+    psFree (fpa->toTPA);
+    fpa->toTPA = toTPAnew;
+
+    // invert toTPA to determine fromTPA. choose an appropriate region based on the dimensions
+    // of the complete FPA
+    psRegion *region = pmAstromFPAExtent (fpa);
+
+    psFree (fpa->fromTPA);
+    fpa->fromTPA = psPlaneTransformInvert(NULL, fpa->toTPA, *region, 50);
+    psFree (region);
+
+    if (fpa->fromTPA == NULL) {
+        psError (PS_ERR_UNKNOWN, false, "failed to invert fpa->toTPA\n");
+	psFree (FPtoTP);
+        return false;
+    }
+
+    psFree (FPtoTP);
+    return true;
+}
+
+// we have three coordinate systems and two polynomial transformations:
+// TP is the raw tangent plane coordinate system (aligned with RA,DEC)
+// FP is the raw focal plane coordinate system (aligned with camera X,Y)
+// dFP is the distorted focal plane coordinate system
+
+// fpa->toTPA is a polynomial transformation from FP to dFP
+//   L(x,y) = \sum_i \sum_j A_{i,j} x^i y^j and 
+//   M(x,y) = \sum_i \sum_j B_{i,j} x^i y^j 
+//   where (x,y) are the FP coords and L,M  are the dFP coords
+
+// FPtoTP is a linear transformation from dFP to TP (we recover this from TPtoFP by inversion)
+// P(L,M) = r_xo + r_xx L + r_xy M
+// Q(L,M) = r_yo + r_yx L + r_yy M
+
+// we need to merge them into a single transformation:
+// P(x,y) = r_xo + r_xx * \sum_i \sum_j A_{i,j} x^i y^j + r_xy * \sum_i \sum_j B_{i,j} x^i y^j 
+// Q(x,y) = r_yo + r_yx * \sum_i \sum_j A_{i,j} x^i y^j + r_yy * \sum_i \sum_j B_{i,j} x^i y^j 
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicDistortion.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicDistortion.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicDistortion.c	(revision 23321)
@@ -0,0 +1,67 @@
+/** @file psastroMosaicDistortion.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+# define DEBUG 0
+
+bool psastroMosaicDistortion (pmFPA *fpa, psMetadata *recipe, int pass) {
+
+    // fit a linear transformation from reference TP to observed FP coords
+    psPlaneTransform *TPtoFP = psastroMosaicFitRotAndScale (fpa);
+    if (!TPtoFP) {
+	psError(PSASTRO_ERR_UNKNOWN, false, "failed to fit a linear TP correction\n");
+        return false;
+    }
+
+    if (DEBUG && (pass == 0)) psastroDumpCorners ("corners.up.v1.dat", "corners.dn.v1.dat", fpa);
+
+    // Correct the current reference star TP coordinates to the nearly-FP
+    // system.  We note two points here: 1) the corrected TP coordinates are
+    // NOT consistent with the sky coordinates, 2) the remaining differnce
+    // between the new TP reference coords and the observerd FP coordinates is
+    // only distortion
+    if (!psastroMosaicApplyRotAndScale (fpa, TPtoFP)) {
+	psError(PSASTRO_ERR_UNKNOWN, false, "failed to apply the linear TP correction\n");
+	psFree (TPtoFP);
+        return false;
+    }
+
+    if (DEBUG && (pass == 0)) psastroDumpCorners ("corners.up.v2.dat", "corners.dn.v2.dat", fpa);
+
+    if (!psastroMosaicDistortionFromGradients (fpa, recipe)) {
+	psError(PSASTRO_ERR_UNKNOWN, false, "failed to fit the distortion field\n");
+	psFree (TPtoFP);
+        return false;
+    }
+
+    if (DEBUG && (pass == 0)) psastroDumpCorners ("corners.up.v3.dat", "corners.dn.v3.dat", fpa);
+
+    if (!psastroMosaicCorrectDistortion (fpa, TPtoFP)) {
+	psError(PSASTRO_ERR_UNKNOWN, false, "failed to correct the distortion for the linear fit\n");
+	psFree (TPtoFP);
+        return false;
+    }
+	
+    if (DEBUG && (pass == 0)) psastroDumpCorners ("corners.up.v4.dat", "corners.dn.v4.dat", fpa);
+
+    if (!psastroMosaicSetAstrom (fpa)) {
+	psError(PSASTRO_ERR_UNKNOWN, false, "failed to apply mosaic distortion terms\n");
+	psFree (TPtoFP);
+        return false;
+    }
+
+    if (DEBUG && (pass == 0)) psastroDumpCorners ("corners.up.v5.dat", "corners.dn.v5.dat", fpa);
+
+    psFree (TPtoFP);
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicFPtoTP.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicFPtoTP.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicFPtoTP.c	(revision 23321)
@@ -0,0 +1,174 @@
+/** @file psastroMosaicFPtoTP.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.3 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+psPlaneTransform *psastroMosaicFitRotAndScale (pmFPA *fpa) {
+
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+
+    // XXX first pass: fit and remove any linear fp->tp transformation
+    // accumulate FP(x,y) & TP(x,y) in a single set of vectors
+
+    psVector *X  = psVectorAllocEmpty(100, PS_TYPE_F32);
+    psVector *Y  = psVectorAllocEmpty(100, PS_TYPE_F32);
+    psVector *x  = psVectorAllocEmpty(100, PS_TYPE_F32);
+    psVector *y  = psVectorAllocEmpty(100, PS_TYPE_F32);
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // int nPts = 0;
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	if (!chip->toFPA) { continue; }
+	
+	while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 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
+	    // XXX there can only be one readout per chip, right?
+	    while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+		if (! readout->data_exists) { continue; }
+
+		// select the raw objects for this readout
+		psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+		if (rawstars == NULL) { continue; }
+
+		// select the raw objects for this readout
+		psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+		if (refstars == NULL) { continue; }
+
+		psArray *match = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.MATCH");
+		if (match == NULL) { continue; }
+
+		// take the matched stars, first fit
+		for (int i = 0; i < match->n; i++) {
+		    pmAstromMatch *pair = match->data[i];
+		    pmAstromObj *rawStar = rawstars->data[pair->raw];
+		    pmAstromObj *refStar = refstars->data[pair->ref];
+
+		    // independent variables
+		    X->data.F32[X->n] = refStar->TP->x;
+		    Y->data.F32[Y->n] = refStar->TP->y;
+
+		    // fitted values
+		    x->data.F32[x->n] = rawStar->FP->x;
+		    y->data.F32[y->n] = rawStar->FP->y;
+
+		    psVectorExtend (X, 100, 1);
+		    psVectorExtend (Y, 100, 1);
+		    psVectorExtend (x, 100, 1);
+		    psVectorExtend (y, 100, 1);
+		}
+	    }
+	}
+    }
+    // x->n = y->n = X->n = Y->n = nPts;
+
+    // linear fit without xy cross term
+    psPlaneTransform *map = psPlaneTransformAlloc (1, 1);
+    map->x->coeffMask[1][1] = PS_POLY_MASK_SET;
+    map->y->coeffMask[1][1] = PS_POLY_MASK_SET;
+
+    // constant errors
+    psVector *mask = psVectorAlloc (X->n, PS_TYPE_VECTOR_MASK);
+    psVectorInit (mask, 0);
+
+    // the stats options supplied are used to perform the clip fitting
+    psStats *stats = psStatsAlloc (PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    stats->clipIter = 1;
+
+    // fit TP-to-FP transformation
+
+    // we run 3 cycles clipping in each of x and y, with only one iteration each.
+    // XXX use the stats lookups functions to get the width and center
+    for (int i = 0; i < 3; i++) {
+	if (!psVectorClipFitPolynomial2D (map->x, stats, mask, 0xff, x, NULL, X, Y)) {
+            psError(PS_ERR_UNKNOWN, false, "failure in clip-fitting for x\n");
+	    psFree (map);
+	    map = NULL;
+	    goto escape;
+	}
+        psTrace ("psastro", 3, "x resid: %f +/- %f (%ld of %ld)\n", stats->clippedMean, stats->clippedStdev, stats->clippedNvalues, x->n);
+
+        if (!psVectorClipFitPolynomial2D (map->y, stats, mask, 0xff, y, NULL, X, Y)) {
+            psError(PS_ERR_UNKNOWN, false, "failure in clip-fitting for y\n");
+	    psFree (map);
+	    map = NULL;
+	    goto escape;
+	}
+        psTrace ("psastro", 3, "y resid: %f +/- %f (%ld of %ld)\n", stats->clippedMean, stats->clippedStdev, stats->clippedNvalues, y->n);
+    }
+
+escape:
+    psFree (x);
+    psFree (y);
+    psFree (X);
+    psFree (Y);
+    psFree (mask);
+    psFree (view);
+    psFree (stats);
+
+    return (map);
+}
+
+bool psastroMosaicApplyRotAndScale (pmFPA *fpa, psPlaneTransform *TPtoFP) {
+
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    psPlane newTP;
+
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	if (!chip->toFPA) { continue; }
+	
+	while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 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
+	    // XXX there can only be one readout per chip, right?
+	    while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+		if (! readout->data_exists) { continue; }
+
+		// select the raw objects for this readout
+		psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+		if (refstars == NULL) { continue; }
+
+		// take the matched stars, first fit
+		for (int i = 0; i < refstars->n; i++) {
+		    pmAstromObj *refStar = refstars->data[i];
+
+		    // Correct the current reference star TP coordinates to the nearly-FP
+		    // system.  We note two points here: 1) the corrected TP coordinates are
+		    // NOT consistent with the sky coordinates, 2) the remaining differnce
+		    // between the new TP reference coords and the observerd FP coordinates is
+		    // only distortion
+
+		    psPlaneTransformApply (&newTP, TPtoFP, refStar->TP);
+		    refStar->TP->x = newTP.x;
+		    refStar->TP->y = newTP.y;
+		}
+	    }
+	}
+    }
+    psFree (view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicGetGrads.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicGetGrads.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicGetGrads.c	(revision 23321)
@@ -0,0 +1,48 @@
+# include "psastroInternal.h"
+
+psArray *psastroMosaicGetGrads (pmFPA *fpa, psMetadata *recipe) {
+
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+    psArray *grads = NULL;
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	
+	psRegion *region = pmChipExtent (chip);
+
+	while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 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
+	    // XXX there can only be one readout per chip, right?
+	    while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+		if (! readout->data_exists) { continue; }
+
+		// select the raw objects for this readout
+		psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+		if (rawstars == NULL) { continue; }
+
+		// select the raw objects for this readout
+		psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+		if (refstars == NULL) { continue; }
+
+		psArray *match = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.MATCH");
+		if (match == NULL) { continue; }
+
+		// measure the local gradients for this set of stars
+		// the new elements are added to the incoming gradient structure 
+		grads = pmAstromMeasureGradients (grads, rawstars, refstars, match, region, 2, 2);
+	    }
+	}
+	psFree (region);
+    }
+    psFree (view);
+    return (grads);
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicGradients.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicGradients.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicGradients.c	(revision 23321)
@@ -0,0 +1,118 @@
+/** @file psastroMosaicGradients.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.11 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+static int nPass = 0;
+
+bool psastroMosaicDistortionFromGradients (pmFPA *fpa, psMetadata *recipe) {
+
+    bool status;
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+    psArray *gradients = NULL;
+
+    // Measure the gradient as a function of position.  This must be performed between the
+    // corrected ref->TP and the observed raw->FP, for which the distortion is a perturbation.
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    int nXcell = psMetadataLookupS32 (&status, recipe, "PSASTRO.MOSAIC.GRADIENT.NX");
+    int nYcell = psMetadataLookupS32 (&status, recipe, "PSASTRO.MOSAIC.GRADIENT.NY");
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	if (!chip->toFPA) { continue; }
+	
+	psRegion *region = pmChipExtent (chip);
+
+	while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 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
+	    // XXX there can only be one readout per chip, right?
+	    while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+		if (! readout->data_exists) { continue; }
+
+		// select the raw objects for this readout
+		psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+		if (rawstars == NULL) { continue; }
+
+		// select the raw objects for this readout
+		psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+		if (refstars == NULL) { continue; }
+
+		psArray *match = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.MATCH");
+		if (match == NULL) { continue; }
+
+		// measure the local gradients for this set of stars
+		// XXX 2,2 are the number of test boxes on the chip.  put this in the recipe
+		gradients = pmAstromMeasureGradients (gradients, rawstars, refstars, match, region, nXcell, nYcell);
+	    }
+	}
+	psFree (region);
+    }
+
+    // if desired, dump the gradients to a file
+    if (psTraceGetLevel("psastro.dump") > 0) { 
+	char name[80];
+	sprintf (name, "gradients.%d.dat", nPass); 
+	psastroDumpGradients (gradients, name); 
+	nPass ++;
+    }
+
+    // Fit the gradient field and convert to the distortion terms.
+
+    // allocate mosaic-level polynomial transformation and set masks needed by DVO
+    int order = psMetadataLookupF32 (&status, recipe, "PSASTRO.MOSAIC.ORDER");
+    if (!status) {
+	psError(PSASTRO_ERR_UNKNOWN, false, "failed to find single-chip fit order\n");
+	psFree (gradients);
+	psFree (view);
+        return false;
+    }
+    psFree (fpa->toTPA);
+    fpa->toTPA = psPlaneTransformAlloc (order, order);
+    for (int i = 0; i <= fpa->toTPA->x->nX; i++) {
+        for (int j = 0; j <= fpa->toTPA->x->nY; j++) {
+            if (i + j > order) {
+		fpa->toTPA->x->coeffMask[i][j] = PS_POLY_MASK_SET;
+		fpa->toTPA->y->coeffMask[i][j] = PS_POLY_MASK_SET;
+            }
+        }
+    }
+
+    // physical pixel scale in microns per pixel (FP is in physical units, chip is in pixels)
+    double pixelScale = psMetadataLookupF32 (&status, recipe, "PSASTRO.PIXEL.SCALE");
+    if (!status) {
+	psError(PS_ERR_IO, false, "Failed to lookup pixel scale"); 
+	psFree (gradients);
+	psFree (view);
+	return false; 
+    } 
+
+    // fit the measured gradients with the telescope distortion model (polynomial order based on toTPA)
+    if (!pmAstromFitDistortion (fpa, gradients, pixelScale)) {
+	psError(PSASTRO_ERR_UNKNOWN, false, "failed to fit the distortion terms\n");
+	psFree (gradients);
+	psFree (view);
+        return false;
+    }
+	
+    psFree (gradients);
+    psFree (view);
+    return true;
+}
+
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicHeaders.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicHeaders.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicHeaders.c	(revision 23321)
@@ -0,0 +1,59 @@
+# include "psastroInternal.h"
+
+bool psastroMosaicHeaders (pmConfig *config) {
+
+    bool status = false;
+    pmChip *chip = NULL;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+ 	psError(PSASTRO_ERR_CONFIG, true, "Can't find PSASTRO recipe!\n");
+	return false;
+    }
+
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+	psError(PSASTRO_ERR_CONFIG, true, "Can't find input data!\n");
+	return false;
+    }
+
+    char *mosastro = psMetadataLookupStr (NULL, config->arguments, "MOSASTRO");
+
+    double plateScale = psMetadataLookupF32 (&status, recipe, "PSASTRO.PLATE.SCALE");
+    if (!status) plateScale = 1.0;
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+    pmFPA *fpa = input->fpa;
+
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+
+        // read WCS data from the corresponding header
+        pmHDU *hdu = pmFPAviewThisHDU (view, fpa);
+
+        pmAstromWriteBilevelChip (chip->toFPA, hdu->header, plateScale);
+    }
+
+    psMetadata *mosaic = pmAstromWriteBilevelMosaic (fpa->toSky, fpa->toTPA, plateScale);
+
+    // XXX what is the EXTNAME??
+    psFits *fits = psFitsOpen (mosastro, "w");
+    psFitsWriteBlank(fits, mosaic, "");
+    psFitsClose (fits);
+
+    psFree (view);
+    return true;
+}
+
+/* coordinate frame hierachy
+   pixels (on a given readout)
+   cell
+   chip
+   FP (focal plane)
+   TP (tangent plane)
+   sky (ra, dec)
+*/
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicOneChip.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicOneChip.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicOneChip.c	(revision 23321)
@@ -0,0 +1,167 @@
+/** @file psastroMosaicOneChip.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.10 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-09 21:25:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+# define REQUIRED_RECIPE_VALUE(VALUE, NAME, TYPE, MESSAGE)\
+  VALUE = psMetadataLookup##TYPE (&status, recipe, NAME); \
+  if (!status) { \
+   psError(PSASTRO_ERR_CONFIG, false, MESSAGE); \
+   return false; }
+
+bool psastroMosaicOneChip (pmChip *chip, pmReadout *readout, psMetadata *recipe, psMetadata *updates, int iteration) {
+
+    bool status;
+    char errorWord[64];
+    char orderWord[64];
+
+    PS_ASSERT_PTR_NON_NULL(chip,    false);
+    PS_ASSERT_PTR_NON_NULL(readout, false);
+    PS_ASSERT_PTR_NON_NULL(recipe,  false);
+    PS_ASSERT_PTR_NON_NULL(updates, false);
+
+    // select the raw objects for this readout
+    psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+    if (rawstars == NULL) return false;
+
+    psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+    if (refstars == NULL) return false;
+
+    psArray *match = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.MATCH");
+    if (match == NULL) return false;
+
+    // correct radius to FP units (physical pixel scale in microns per pixel)
+    REQUIRED_RECIPE_VALUE (double pixelScale, "PSASTRO.PIXEL.SCALE", F32, "Failed to lookup pixel scale");
+
+    // allowed limits for valid solutions
+    snprintf (errorWord, 64, "PSASTRO.MOSAIC.MAX.ERROR.N%d", iteration);
+    REQUIRED_RECIPE_VALUE (float maxError, errorWord, F32, "failed to find single-chip max allowed error\n");
+    REQUIRED_RECIPE_VALUE (int minNstar, "PSASTRO.MOSAIC.MIN.NSTAR", S32, "failed to find single-chip min allowed stars\n");
+
+    // set the order of the per-chip fit (higher order only if iteration > 0)
+    REQUIRED_RECIPE_VALUE (int defaultOrder, "PSASTRO.MOSAIC.CHIP.ORDER", S32, "failed to find mosaic chip-level fit default order\n");
+
+    snprintf (orderWord, 64, "PSASTRO.MOSAIC.CHIP.ORDER.N%d", iteration);
+    int order = psMetadataLookupS32 (&status, recipe, orderWord);
+    if (!status || (order == -1)) {
+        order = defaultOrder;
+    }
+
+    // modify the order to correspond to the actual number of matched stars:
+    if ((match->n < 17) && (order >= 3)) order = 2;
+    if ((match->n < 13) && (order >= 2)) order = 1;
+    if ((match->n <  9) && (order >= 1)) order = 0;
+    if ((match->n <  3) || (order < 0) || (order > 3)) {
+        psLogMsg ("psastro", 3, "insufficient stars (%ld) or invalid order (%d)", match->n, order);
+        return false;
+    }
+
+    psLogMsg ("psastro", PS_LOG_DETAIL, "mosaic fit chip order %d", order);
+
+    // create output toFPA; set masks appropriate to the Elixir DVO astrometry format if we are
+    // fitting 0th order, use the current polynomial of whatever order, with higher order
+    // coefficients frozen to the current values
+    if (order == 0) {
+        // set FIT mask for all higher order terms of the existing solution
+        // any existing SET masks will be retained.
+        for (int i = 0; i <= chip->toFPA->x->nX; i++) {
+            for (int j = 0; j <= chip->toFPA->x->nY; j++) {
+                if (i + j > 0) {
+                    chip->toFPA->x->coeffMask[i][j] |= PS_POLY_MASK_FIT;
+                    chip->toFPA->y->coeffMask[i][j] |= PS_POLY_MASK_FIT;
+                }
+            }
+        }
+    } else {
+        psFree (chip->toFPA);
+        chip->toFPA = psPlaneTransformAlloc (order, order);
+        for (int i = 0; i <= chip->toFPA->x->nX; i++) {
+            for (int j = 0; j <= chip->toFPA->x->nY; j++) {
+                if (i + j > order) {
+                    chip->toFPA->x->coeffMask[i][j] = PS_POLY_MASK_SET;
+                    chip->toFPA->y->coeffMask[i][j] = PS_POLY_MASK_SET;
+                }
+            }
+        }
+    }
+
+    // XXX allow statitic to be set by the user
+    // only clip if we are fitting the chip parameters.
+    psStats *fitStats = NULL;
+//    if (order == 0) {
+//      fitStats = psStatsAlloc (PS_STAT_CLIPPED_MEAN | PS_STAT_CLIPPED_STDEV);
+//      fitStats->clipSigma = psMetadataLookupF32 (&status, recipe, "PSASTRO.MOSAIC.CHIP.NSIGMA");
+//      fitStats->clipIter = psMetadataLookupS32 (&status, recipe, "PSASTRO.MOSAIC.CHIP.NITER");
+//    } else {
+        fitStats = psStatsAlloc (PS_STAT_CLIPPED_MEAN | PS_STAT_CLIPPED_STDEV);
+        fitStats->clipSigma = psMetadataLookupF32 (&status, recipe, "PSASTRO.MOSAIC.CHIP.NSIGMA");
+        fitStats->clipIter = psMetadataLookupS32 (&status, recipe, "PSASTRO.MOSAIC.CHIP.NITER");
+//    }
+
+    // need to pass in an update header, sent in from above
+    pmAstromFitResults *results = pmAstromMatchFit (chip->toFPA, rawstars, refstars, match, fitStats);
+    if (!results) {
+        psError(PSASTRO_ERR_DATA, false, "failed to perform the matched fit\n");
+        return false;
+    }
+
+    // toSky converts from FPA & TPA units (microns) to sky units (radians)
+    pmFPA *fpa = chip->parent;
+    float plateScale = 0.5*(fpa->toSky->Xs + fpa->toSky->Ys)*3600.0*PM_DEG_RAD;
+
+    // pixError is the average 1D scatter in pixels ('results' are in FPA units = microns)
+    float pixError = 0.5*(results->xStats->clippedStdev + results->yStats->clippedStdev) / pixelScale;
+
+    // astError is the average 1D scatter in arcsec ('results' are in FPA units = microns)
+    float astError = 0.5*(results->xStats->clippedStdev + results->yStats->clippedStdev) * plateScale;
+    int astNstar = results->yStats->clippedNvalues;
+
+    // if we clip away too many stars, the order may be invalid
+    if (order == 3) { minNstar = PS_MAX (15, minNstar); }
+    if (order == 2) { minNstar = PS_MAX (11, minNstar); }
+    if (order == 1) { minNstar = PS_MAX ( 8, minNstar); }
+
+    bool validSolution = true;
+
+    // XXX should these result in errors or be handled another way?
+    psLogMsg ("psastro", PS_LOG_INFO, "astrometry solution: error: %f arcsec, Nstars: %d", astError, astNstar);
+    if ((maxError > 0) && (astError > maxError)) {
+        psLogMsg("psastro", PS_LOG_INFO, "residual error is too large, failed to find a solution: %f > %f", astError, maxError);
+        validSolution = false;
+    }
+    if (astNstar < minNstar) {
+        psLogMsg("psastro", PS_LOG_INFO, "solution uses too few stars: %d < %d", astNstar, minNstar);
+        validSolution = false;
+    }
+
+    // DVO expects NASTRO = 0 if we fail to find a solution
+    psMetadataAddF32 (updates, PS_LIST_TAIL, "PERROR",   PS_META_REPLACE, "astrometry error (pixels)", pixError);
+    psMetadataAddF32 (updates, PS_LIST_TAIL, "CERROR",   PS_META_REPLACE, "astrometry error (arcsec)", astError);
+    if (validSolution) {
+        psMetadataAddF32 (updates, PS_LIST_TAIL, "CPRECISE", PS_META_REPLACE, "astrometry precision (arcsec)", astError/sqrt(astNstar));
+        psMetadataAddS32 (updates, PS_LIST_TAIL, "NASTRO",   PS_META_REPLACE, "number of astrometry stars", astNstar);
+    } else {
+        psMetadataAddF32 (updates, PS_LIST_TAIL, "CPRECISE", PS_META_REPLACE, "astrometry precision (arcsec)", 0.0);
+        psMetadataAddS32 (updates, PS_LIST_TAIL, "NASTRO",   PS_META_REPLACE, "number of astrometry stars", 0);
+    }
+    psMetadataAddF32 (updates, PS_LIST_TAIL, "EQUINOX",  PS_META_REPLACE, "", 2000.0); // XXX this is bogus: should be defined based on equinox of refstars
+
+    // determine fromFPA transformation and apply new transformation to raw & ref stars
+    psastroUpdateChipToFPA (fpa, chip, rawstars, refstars);
+
+    //plot results
+    pmAstromVisualPlotMosaicOneChip(rawstars, refstars, match, recipe);
+
+    psFree (fitStats);
+    psFree (results);
+    return validSolution;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicSetAstrom.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicSetAstrom.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicSetAstrom.c	(revision 23321)
@@ -0,0 +1,64 @@
+/** @file psastroMosaicSetAstrom.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.12 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+bool psastroMosaicSetAstrom (pmFPA *fpa) {
+
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	if (!chip->toFPA) { continue; }
+	
+	while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 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
+	    // XXX there can only be one readout per chip, right?
+	    while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+		if (! readout->data_exists) { continue; }
+
+		// select the raw objects for this readout
+		psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+		if (rawstars == NULL) { continue; }
+
+		for (int i = 0; i < rawstars->n; i++) {
+		    pmAstromObj *raw = rawstars->data[i];
+	
+		    psPlaneTransformApply (raw->FP, chip->toFPA, raw->chip);
+		    psPlaneTransformApply (raw->TP, fpa->toTPA, raw->FP);
+		    psDeproject (raw->sky, raw->TP, fpa->toSky);
+		}
+
+		psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+		if (refstars == NULL) { continue; }
+
+		for (int i = 0; i < refstars->n; i++) {
+		    pmAstromObj *ref = refstars->data[i];
+	
+		    psProject (ref->TP, ref->sky, fpa->toSky);
+		    psPlaneTransformApply (ref->FP, fpa->fromTPA, ref->TP);
+		    psPlaneTransformApply (ref->chip, chip->fromFPA, ref->FP);
+		}
+	    }
+	}
+    }
+    psFree (view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicSetMatch.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicSetMatch.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroMosaicSetMatch.c	(revision 23321)
@@ -0,0 +1,81 @@
+/** @file psastroMosaicSetMatch.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.15 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-09 21:25:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+bool psastroMosaicSetMatch (pmFPA *fpa, psMetadata *recipe, int iteration) {
+
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+    pmFPAview *view = pmFPAviewAlloc (0);
+    char radiusWord[64];
+
+    // use small radius to match stars (assume starting astrometry is good)
+    bool status = false;
+    sprintf (radiusWord, "PSASTRO.MOSAIC.RADIUS.N%d", iteration);
+    double RADIUS = psMetadataLookupF32 (&status, recipe, radiusWord);
+    if (!status) {
+        psError(PS_ERR_IO, false, "Failed to lookup matching radius: %s", radiusWord);
+        psFree (view);
+        return false;
+    }
+
+    if (RADIUS <= 0.0) {
+        if (iteration == 0) {
+            psError(PS_ERR_IO, false, "Invalid match radius for first iteration: %s", radiusWord);
+            psFree (view);
+            return false;
+        }
+        psWarning ("skipping match for iteration %d\n", iteration);
+        psFree (view);
+        return true;
+    }
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+        if (!chip->fromFPA) { continue; }
+
+        while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 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
+            // XXX there can only be one readout per chip, right?
+            while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+                if (! readout->data_exists) { continue; }
+
+                // select the raw objects for this readout
+                psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+                if (rawstars == NULL) { continue; }
+
+                // select the raw objects for this readout
+                psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+                if (refstars == NULL) { continue; }
+                psTrace ("psastro", 4, "Trying %ld refstars\n", refstars->n);
+
+                psArray *matches = pmAstromRadiusMatchChip (rawstars, refstars, RADIUS);
+                psTrace ("psastro", 4, "Matched %ld refstars\n", matches->n);
+
+                pmAstromVisualPlotMosaicMatches(rawstars, refstars, matches, iteration, recipe);
+
+                // XXX drop the old one
+                psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSASTRO.MATCH", PS_DATA_ARRAY | PS_META_REPLACE, "astrometry matches", matches);
+                psFree (matches);
+            }
+        }
+    }
+    psFree (view);
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroOneChipFit.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroOneChipFit.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroOneChipFit.c	(revision 23321)
@@ -0,0 +1,185 @@
+/** @file psastroOneChipFit.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-09 21:25:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+# define REQUIRED_RECIPE_VALUE(VALUE, NAME, TYPE)\
+  VALUE = psMetadataLookup##TYPE (&status, recipe, NAME); \
+  if (!status) { \
+   psAbort ("Failed to find %s in recipe", NAME); }
+
+bool psastroOneChipFit (pmFPA *fpa, pmChip *chip, psArray *refstars, psArray *rawstars, psMetadata *recipe, psMetadata *updates) {
+
+    bool status;
+
+    // default value for match/fit : radius is in pixels
+    REQUIRED_RECIPE_VALUE (double RADIUS, "PSASTRO.MATCH.RADIUS", F32);
+
+    // run the match/fit sequence NITER times
+    REQUIRED_RECIPE_VALUE (int nIter, "PSASTRO.MATCH.FIT.NITER", S32);
+
+    // correct radius to FP units (physical pixel scale in microns per pixel)
+    REQUIRED_RECIPE_VALUE (double pixelScale, "PSASTRO.PIXEL.SCALE", F32);
+    RADIUS *= pixelScale;
+
+    // select the desired chip order
+    REQUIRED_RECIPE_VALUE (int order, "PSASTRO.CHIP.ORDER", S32);
+
+    // allowed limits for valid solutions
+    REQUIRED_RECIPE_VALUE (float maxError, "PSASTRO.MAX.ERROR", F32);
+    REQUIRED_RECIPE_VALUE (int minNstar, "PSASTRO.MIN.NSTAR", S32);
+
+    psArray *match = NULL;
+    psStats *fitStats = NULL;
+    pmAstromFitResults *results = NULL;
+
+    for (int iter = 0; iter < nIter; iter++) {
+
+        char name[128];
+
+        sprintf (name, "PSASTRO.MATCH.RADIUS.N%d", iter);
+        float radius = psMetadataLookupF32 (&status, recipe, name);
+        radius *= pixelScale;
+        if (!status || (radius == 0.0)) {
+            radius = RADIUS;
+        }
+
+
+        // use small radius to match stars
+        match = pmAstromRadiusMatchFP (rawstars, refstars, radius);
+        if (match == NULL) {
+            psLogMsg ("psastro", 3, "failed to find radius-matched sources\n");
+            return false;
+        }
+
+        // modify the order to correspond to the actual number of matched stars:
+        int Ndof_min = 3;
+        int order_max = 0.5*(3 + sqrt(4*match->n - 4*Ndof_min + 1));
+        order = PS_MIN (order, order_max);
+
+        // if ((match->n < 11) && (order >= 3)) order = 2;
+        // if ((match->n <  7) && (order >= 2)) order = 1;
+        // if ((match->n <  4) && (order >= 1)) order = 0;
+
+        if (order < 1) {
+            psLogMsg ("psastro", 3, "insufficient stars or invalid order: %ld stars", match->n);
+            psFree (match);
+            return false;
+        }
+
+        // create output toFPA; set masks appropriate to the Elixir DVO astrometry format
+        psFree (chip->toFPA);
+        chip->toFPA = psPlaneTransformAlloc (order, order);
+        for (int i = 0; i <= chip->toFPA->x->nX; i++) {
+            for (int j = 0; j <= chip->toFPA->x->nY; j++) {
+                if (i + j > order) {
+                    chip->toFPA->x->coeffMask[i][j] = PS_POLY_MASK_SET;
+                    chip->toFPA->y->coeffMask[i][j] = PS_POLY_MASK_SET;
+                }
+            }
+        }
+
+        // XXX allow statitic to be set by the user
+        // fitStats = psStatsAlloc (PS_STAT_CLIPPED_MEAN | PS_STAT_CLIPPED_STDEV);
+        fitStats = psStatsAlloc (PS_STAT_ROBUST_MEDIAN | PS_STAT_ROBUST_STDEV);
+        fitStats->clipSigma = psMetadataLookupF32 (&status, recipe, "PSASTRO.CHIP.NSIGMA");
+        fitStats->clipIter = psMetadataLookupS32 (&status, recipe, "PSASTRO.CHIP.NITER");
+
+        // improved fit for astrometric terms
+        results = pmAstromMatchFit (chip->toFPA, rawstars, refstars, match, fitStats);
+        if (!results) {
+            psLogMsg ("psastro", 3, "failed to perform the matched fit\n");
+            psFree (match);
+            psFree (fitStats);
+            return false;
+        }
+
+        // determine fromFPA transformation and apply new transformation to raw & ref stars
+        psastroUpdateChipToFPA (fpa, chip, rawstars, refstars);
+
+        // toSky converts from FPA & TPA units (microns) to sky units (radians)
+        float plateScale = 0.5*(fpa->toSky->Xs + fpa->toSky->Ys)*3600.0*PM_DEG_RAD;
+        // float astError = 0.5*(results->xStats->clippedStdev + results->yStats->clippedStdev) * plateScale;
+        float astError = 0.5*(results->xStats->robustStdev + results->yStats->robustStdev) * plateScale;
+        int astNstar = results->yStats->clippedNvalues;
+        psLogMsg ("psastro", PS_LOG_INFO, "pass %d, error: %f arcsec, Nstars: %d", iter, astError, astNstar);
+
+        if (iter < nIter - 1) {
+            psFree (fitStats);
+            psFree (results);
+            psFree (match);
+        }
+    }
+
+    // toSky converts from FPA & TPA units (microns) to sky units (radians)
+    float plateScale = 0.5*(fpa->toSky->Xs + fpa->toSky->Ys)*3600.0*PM_DEG_RAD;
+
+    // pixError is the average 1D scatter in pixels ('results' are in FPA units = microns)
+    // float pixError = 0.5*(results->xStats->clippedStdev + results->yStats->clippedStdev) / pixelScale;
+    float pixError = 0.5*(results->xStats->robustStdev + results->yStats->robustStdev) / pixelScale;
+
+    // astError is the average 1D scatter in arcsec ('results' are in FPA units = microns)
+    // float astError = 0.5*(results->xStats->clippedStdev + results->yStats->clippedStdev) * plateScale;
+    float astError = 0.5*(results->xStats->robustStdev + results->yStats->robustStdev) * plateScale;
+    int astNstar = results->yStats->clippedNvalues;
+
+    bool validSolution = true;
+
+    // XXX should these result in errors or be handled another way?
+    psLogMsg ("psastro", PS_LOG_INFO, "astrometry solution: error: %f arcsec, Nstars: %d", astError, astNstar);
+    if (astError > maxError) {
+        psLogMsg("psastro", PS_LOG_INFO, "residual error is too large, failed to find a solution: %f > %f", astError, maxError);
+        validSolution = false;
+    }
+    if (astNstar < minNstar) {
+        psLogMsg("psastro", PS_LOG_INFO, "solution uses too few stars: %d < %d", astNstar, minNstar);
+        validSolution = false;
+    }
+
+    // DVO expects NASTRO = 0 if we fail to find a solution
+    psMetadataAddF32 (updates, PS_LIST_TAIL, "PERROR",   PS_META_REPLACE, "astrometry error (pixels)", pixError);
+    psMetadataAddF32 (updates, PS_LIST_TAIL, "CERROR",   PS_META_REPLACE, "astrometry error (arcsec)", astError);
+    if (validSolution) {
+        psMetadataAddF32 (updates, PS_LIST_TAIL, "CPRECISE", PS_META_REPLACE, "astrometry precision (arcsec)", astError/sqrt(astNstar));
+        psMetadataAddS32 (updates, PS_LIST_TAIL, "NASTRO",   PS_META_REPLACE, "number of astrometry stars", astNstar);
+    } else {
+        psMetadataAddF32 (updates, PS_LIST_TAIL, "CPRECISE", PS_META_REPLACE, "astrometry precision (arcsec)", 0.0);
+        psMetadataAddS32 (updates, PS_LIST_TAIL, "NASTRO",   PS_META_REPLACE, "number of astrometry stars", 0);
+    }
+    psMetadataAddF32 (updates, PS_LIST_TAIL, "EQUINOX",  PS_META_REPLACE, "equinox of ref catalog", 2000.0); // XXX this is bogus: should be defined based on equinox of refstars
+
+    // XXX drop from here : determine fromFPA transformation and apply new transformation to raw & ref stars
+    // psastroUpdateChipToFPA (fpa, chip, rawstars, refstars);
+
+    // XXX check if we correctly applied the new transformation:
+    if (psTraceGetLevel("psastro.dump") > 0) {
+        psastroDumpRawstars (rawstars, fpa, chip);
+        psastroDumpMatchedStars ("match.dat", rawstars, refstars, match);
+        psastroDumpStars (refstars, "refstars.cal.dat");
+    }
+
+    pmAstromVisualPlotOneChipFit (rawstars, refstars, match, recipe);
+
+    if (psTraceGetLevel("psastro.plot") > 0) {
+        psastroPlotOneChipFit (rawstars, refstars, match, recipe);
+    }
+
+    psFree (match);
+    psFree (results);
+    psFree (fitStats);
+    return validSolution;
+}
+
+// psastroWriteStars ("raw.1.dat", rawstars);
+// psastroWriteStars ("ref.1.dat", refstars);
+// psastroWriteTransform (chip->toFPA);
+
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroOneChipGrid.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroOneChipGrid.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroOneChipGrid.c	(revision 23321)
@@ -0,0 +1,76 @@
+/** @file psastroOneChipGrid.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.2 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+# define REQUIRED_RECIPE_VALUE(VALUE, NAME, TYPE)\
+  VALUE = psMetadataLookup##TYPE (&status, recipe, NAME); \
+  if (!status) { \
+   psAbort ("Failed to find %s in recipe", NAME); }
+
+bool psastroOneChipGrid (pmFPA *fpa, pmChip *chip, psArray *refstars, psArray *rawstars, psMetadata *recipe, psMetadata *updates) {
+
+    bool status;
+    pmAstromStats *stats = NULL;
+
+    // do we need to get a rough initial match?
+    REQUIRED_RECIPE_VALUE (bool gridSearch, "PSASTRO.GRID.SEARCH", Bool);
+    if (!gridSearch) return true;
+
+    // do we need to get a rough initial match?
+    REQUIRED_RECIPE_VALUE (int maxNstar, "PSASTRO.GRID.NSTAR.MAX", S32);
+
+    // generate the bright subset of maxNstar entries (note: rawstars is sorted by S/N)
+    psArray *subset = psArrayAlloc (PS_MIN (maxNstar, rawstars->n));
+    for (int i = 0; (i < maxNstar) && (i < rawstars->n); i++) {
+	subset->data[i] = psMemIncrRefCounter (rawstars->data[i]);
+    }
+
+    // XXX set clump scale from recipe
+    psArray *gridStars = psastroRemoveClumps (subset, 150);
+    psFree (subset);
+
+    psArray *refSubset = psastroRemoveClumps (refstars, 150);
+
+    psLogMsg ("psastro", 3, "grid search using %ld raw vs %ld ref stars\n", gridStars->n, refSubset->n);
+
+    // find initial offset / rotation / scale
+    pmAstromStats *gridStats = pmAstromGridMatch (gridStars, refSubset, recipe);
+    if (gridStats == NULL) {
+	psLogMsg ("psastro", 3, "failed to find a grid match solution\n");
+	psFree (gridStars);
+	psFree (refSubset);
+	return false;
+    }
+    psLogMsg ("psastro", 3, "basic grid search result - offset: %f,%f pixels, rotation: %f deg\n", gridStats->offset.x, gridStats->offset.y, DEG_RAD*gridStats->angle);
+
+    // tweak the position by finding peak of matches stars
+    stats = pmAstromGridTweak (gridStars, refSubset, recipe, gridStats);
+    if (stats == NULL) {
+	psLogMsg ("psastro", 3, "failed to measure tweaked grid solution\n");
+	psFree (gridStats);
+	psFree (gridStars);
+	psFree (refSubset);
+	return false;
+    }
+    psLogMsg ("psastro", 3, "tweak grid search result - offset: %f,%f pixels, rotation: %f deg\n", stats->offset.x, stats->offset.y, DEG_RAD*stats->angle);
+
+    // adjust the chip.toFPA terms only
+    pmAstromGridApply (chip->toFPA, stats);
+    psastroUpdateChipToFPA (fpa, chip, rawstars, refstars);
+    psFree (gridStats);
+    psFree (gridStars);
+    psFree (refSubset);
+    psFree (stats);
+
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroParseCamera.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroParseCamera.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroParseCamera.c	(revision 23321)
@@ -0,0 +1,58 @@
+/** @file psastroParseCamera.c
+ *
+ *  @brief
+ *
+ *  @ingroup psastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.18 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+bool psastroParseCamera (pmConfig *config) {
+
+    bool status = false;
+
+    // the input image(s) are required arguments; they define the camera
+    pmFPAfile *input = pmFPAfileDefineFromArgs (&status, config, "PSASTRO.INPUT", "INPUT");
+    if (!status) {
+	psError(PSASTRO_ERR_CONFIG, false, "Failed to build FPA from PSASTRO.INPUT");
+	return false;
+    }
+
+    // define the additional input/output files associated with psphot 
+    if (!psastroDefineFiles (config, input)) {
+	psError(PSASTRO_ERR_CONFIG, false, "Trouble defining the additional input/output files");
+	return false;
+    }
+
+    // Chip selection: turn on only the chips specified (option is not required)
+    char *chipLine = psMetadataLookupStr(&status, config->arguments, "CHIP_SELECTIONS"); 
+    psArray *chips = psStringSplitArray (chipLine, ",", false);
+    if (chips->n > 0) {
+	pmFPASelectChip (input->fpa, -1, true); // deselect all chips
+	for (int i = 0; i < chips->n; i++) {
+	    int chipNum = atoi(chips->data[i]);
+	    if (! pmFPASelectChip(input->fpa, chipNum, false)) {
+		psError(PSASTRO_ERR_CONFIG, true, "Chip number %d doesn't exist in camera.\n", chipNum);
+		return false;
+	    }
+        }
+    }
+    psFree (chips);
+
+    psString dump_file = psMetadataLookupStr(&status, config->arguments, "DUMP_CONFIG");
+    if (dump_file) {
+        pmConfigCamerasCull(config, NULL);
+        pmConfigRecipesCull(config, "PPIMAGE,PPSTATS,PSPHOT,MASKS,PSASTRO");
+
+        pmConfigDump(config, input->fpa, dump_file);
+    }
+
+
+    psTrace("psastro", 1, "Done with psastroParseCamera...\n");
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroRefstarSubset.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroRefstarSubset.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroRefstarSubset.c	(revision 23321)
@@ -0,0 +1,102 @@
+/** @file psastroRefstarSubset.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.13 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+bool psastroRefstarSubset (pmReadout *readout) {
+
+  // select the raw objects for this readout
+  psArray *rawstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.RAWSTARS");
+  if (rawstars == NULL)  {
+    psError(PSASTRO_ERR_DATA, false, "missing rawstars in psastroRefstarSubset\n");
+    return false;
+  }
+
+  // select the raw objects for this readout
+  psArray *refstars = psMetadataLookupPtr (NULL, readout->analysis, "PSASTRO.REFSTARS");
+  if (refstars == NULL)  {
+    psError(PSASTRO_ERR_DATA, false, "missing refstars in psastroRefstarSubset\n");
+    return false;
+  }
+
+  // calculate luminosity functions for rawstars and refstars
+  // the samples cover the same area (the chip), so no area correction
+  // is needed...
+  psLogMsg ("psastro", 4, "measuring luminosity function for rawstars\n");
+  pmLumFunc *rawfunc = psastroLuminosityFunction (rawstars, NULL);
+  if (rawfunc == NULL) {
+    psLogMsg ("psastro", 4, "giving up on rawstars for this readout\n");
+    return true;
+  }
+  psLogMsg ("psastro", 4, "measuring luminosity function for refstars\n");
+  pmLumFunc *reffunc = psastroLuminosityFunction (refstars, rawfunc);
+  if (reffunc == NULL) {
+    psLogMsg ("psastro", 4, "giving up on refstars for this readout\n");
+    return true;
+  }
+
+// XXX code to better deal with mismatches in the fitted lum function
+# if (0)
+  // if the fitted slopes differ by too much, give up and just try to match the peak bin
+  fSlope = (reffunc->slope / rawfunc->slope);
+  if ((fSlope > 1.3) || (fSlope < 0.77)) {
+      // XXX do something here (choose the peak of the smaller set, generate a histogram for
+      // the other set, then choose the bin from the larger set which has nBin = nPeak
+  }
+# endif
+
+  // what is the offset between the two lines at the average magnitude?
+  double mRef = 0.5*(reffunc->mMin + reffunc->mMax);
+  double logRho = mRef * reffunc->slope + reffunc->offset;
+  double mRaw = (logRho - rawfunc->offset) / rawfunc->slope;
+
+  psLogMsg ("psastro", 4, "mRef: %f, logRho: %f, mRaw: %f\n", mRef, logRho, mRaw);
+
+  double mRefMax = rawfunc->mMax - mRaw + mRef;
+  psLogMsg ("psastro", 4, "clipping stars fainter than %f\n", mRefMax);
+
+  psArray *subset = psArrayAllocEmpty (100);
+  for (int i = 0; i < refstars->n; i++) {
+    pmAstromObj *ref = refstars->data[i];
+    if (ref->Mag > mRefMax) continue;
+    psArrayAdd (subset, 100, ref);
+  }
+
+  psLogMsg ("psastro", 4, "keeping %ld of %ld reference stars\n", subset->n, refstars->n);
+
+  psMetadataRemoveKey (readout->analysis, "PSASTRO.REFSTARS");
+  psMetadataAdd (readout->analysis, PS_LIST_TAIL, "PSASTRO.REFSTARS", PS_DATA_ARRAY, "astrometry matches", subset);
+
+  if (psTraceGetLevel("psastro.dump") > 0) {
+      pmChip *chip = readout->parent->parent;
+
+      char *filename = NULL;
+      char *chipname = psMetadataLookupStr (NULL, chip->concepts, "CHIP.NAME");
+      psStringAppend (&filename, "refstars.%s.dat", chipname);
+      psastroDumpRefstars (subset, filename);
+      psFree (filename);
+  }
+
+  psFree (rawfunc);
+  psFree (reffunc);
+  psFree (subset);
+
+  return true;
+}
+
+/* this test is a bit sensitive to the total number of refstars or rawstars available
+   watch out if:
+   - the fitted slopes are extremely different
+   - the average number of stars per bin is ~1
+
+   skip the cut in these cases?
+*/
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroRemoveClumps.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroRemoveClumps.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroRemoveClumps.c	(revision 23321)
@@ -0,0 +1,115 @@
+/** @file psastroRemoveClumps.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.6 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-09 21:25:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+/**
+ * look for and exclude objects in clumps in the input list
+ */
+psArray *psastroRemoveClumps (psArray *input, int scale) {
+
+    // determine data range
+    pmAstromObj *obj = input->data[0];
+    float Xmin = obj->FP->x;
+    float Xmax = obj->FP->x;
+    float Ymin = obj->FP->y;
+    float Ymax = obj->FP->y;
+    for (int i = 0; i < input->n; i++) {
+        obj = (pmAstromObj *)input->data[i];
+        if (!isfinite(obj->FP->x)) continue;
+        if (!isfinite(obj->FP->y)) continue;
+        Xmin = PS_MIN (Xmin, obj->FP->x);
+        Xmax = PS_MAX (Xmax, obj->FP->x);
+        Ymin = PS_MIN (Ymin, obj->FP->y);
+        Ymax = PS_MAX (Ymax, obj->FP->y);
+    }
+
+    int nX = (Xmax - Xmin) / scale + 10;
+    int nY = (Ymax - Ymin) / scale + 10;
+    psImage *count = psImageAlloc (nX, nY, PS_TYPE_U32);
+    psImageInit (count, 0);
+
+    // accumulate 2D histogram in image
+    for (int i = 0; i < input->n; i++) {
+        obj = (pmAstromObj *)input->data[i];
+        if (!isfinite(obj->FP->x)) continue;
+        if (!isfinite(obj->FP->y)) continue;
+        int Xi = PS_MIN (PS_MAX((obj->FP->x - Xmin) / scale + 5, 0), count->numCols);
+        int Yi = PS_MIN (PS_MAX((obj->FP->y - Ymin) / scale + 5, 0), count->numRows);
+        count->data.U32[Yi][Xi] ++;
+    }
+
+    // determine image statistics
+    psStats *stats = psStatsAlloc (PS_STAT_MAX | PS_STAT_MAX | PS_STAT_SAMPLE_MEDIAN | PS_STAT_SAMPLE_STDEV);
+    if (!psImageStats(stats, count, NULL, 0)) {
+        psError(PS_ERR_UNKNOWN, false, "Unable to get image statistics.\n");
+        psFree(stats);
+        psFree(count);
+        return NULL;
+    }
+
+    if (stats->max < 1) {
+        psError(PS_ERR_UNKNOWN, false, "no valid sources in image\n");
+        psFree(stats);
+        psFree(count);
+        return NULL;
+    }
+
+    // XXX make this a user option
+    float limit = PS_MAX (5.0*stats->sampleStdev, 5.0);
+    psTrace ("psastro", 4, "skipping stars in cells with more than %f stars\n", limit);
+
+    pmAstromVisualPlotRemoveClumps (input, count, scale, limit);
+
+    // find and exclude objects in bad pixels
+    psArray *output = psArrayAllocEmpty (input->n);
+    for (int i = 0; i < input->n; i++) {
+        obj = (pmAstromObj *)input->data[i];
+        if (!isfinite(obj->FP->x)) continue;
+        if (!isfinite(obj->FP->y)) continue;
+        int Xi = PS_MIN (PS_MAX((obj->FP->x - Xmin) / scale + 5, 0), count->numCols);
+        int Yi = PS_MIN (PS_MAX((obj->FP->y - Ymin) / scale + 5, 0), count->numRows);
+        if (count->data.U32[Yi][Xi] > limit) continue;
+        psArrayAdd (output, 16, obj);
+    }
+
+    psFree(stats);
+    psFree(count);
+    return output;
+}
+
+# if (0)
+/**
+ * make a list of the outlier pixels
+ */
+psArray *badpix = psArrayAllocEmpty (16);
+for (int iy = 0; iy < count->numRows; iy++) {
+    for (int ix = 0; ix < count->numCols; ix++) {
+        if (count->data.U32[iy][ix] > limit) {
+            psPlane *pixel = psPlaneAlloc();
+            pixel->x = ix;
+            pixel->y = iy;
+            psArrayAdd (badpix, 16, pixel);
+            psFree (pixel);
+        }
+    }
+}
+
+if (badpix->n == 0) {
+    psArray *output = psMemIncrRefCounter (input);
+    psFree (stats);
+    psFree (count);
+    psFree (badpix);
+    return output;
+}
+# endif
+
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroStandAlone.h
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroStandAlone.h	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroStandAlone.h	(revision 23321)
@@ -0,0 +1,50 @@
+/** @file psastroStandAlone.h
+ *
+ *  @brief
+ *
+ *  @ingroup psastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# ifdef HAVE_CONFIG_H
+# include <config.h>
+# endif
+
+#ifndef PSASTRO_STAND_ALONE_H
+#define PSASTRO_STAND_ALONE_H
+
+#include <stdio.h>
+#include <string.h>
+#include <pslib.h>
+#include <psmodules.h>
+#include "psastro.h"
+
+// Top level functions
+pmConfig         *psastroArguments (int argc, char **argv);
+void              psastroCleanup (pmConfig *config);
+bool              psastroParseCamera (pmConfig *config);
+bool              psastroDataLoad (pmConfig *config);
+
+pmConfig         *psastroModelArguments (int argc, char **argv);
+bool 		  psastroModelParseCamera (pmConfig *config);
+bool 		  psastroModelDataLoad (pmConfig *config);
+bool 		  psastroModelAnalysis (pmConfig *config);
+bool 		  psastroModelAdjust (pmConfig *config);
+bool 		  psastroModelDataSave (pmConfig *config);
+
+psVector         *psastroModelFitBoresite (psVector *Xo, psVector *Yo, psVector *Po, char *outroot);
+psF32 		  psastroModelBoresite (psVector *deriv, const psVector *params, const psVector *coord);
+
+// these are used to define the boresite model parameters
+# define PAR_X0  0  // Xo = params[0] 
+# define PAR_Y0  1  // Yo = params[1] 
+# define PAR_RX  2  // RX = params[2] 
+# define PAR_RY  3  // RY = params[3] 
+# define PAR_P0  4  // P0 = params[4]
+# define PAR_T0  5  // phi = params[4]
+
+#endif
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroTestFuncs.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroTestFuncs.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroTestFuncs.c	(revision 23321)
@@ -0,0 +1,59 @@
+/** @file psastroTestFuncs.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.7 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+/**
+ * write out objects
+ */
+bool psastroWriteStars (char *filename, psArray *sources) {
+
+    // re-open, add data to end of file
+    FILE *f = fopen (filename, "w");
+    if (f == NULL) {
+	psLogMsg ("psastroWriteStars", 3, "can't open output file for output %s\n", filename);
+	return false;
+    }
+
+    for (int i = 0; i < sources->n; i++) {
+	
+	pmAstromObj *star = sources->data[i];
+
+	fprintf (f, "%8.2f %8.2f   %8.2f %8.2f   %8.2f %8.2f   %10.6f %10.6f   %8.2f %8.2f\n", 
+		 star->chip->x, star->chip->y, 
+		 star->FP->x, star->FP->y, 
+		 star->TP->x, star->TP->y, 
+		 star->sky->r*DEG_RAD, star->sky->d*DEG_RAD, 
+		 star->Mag, star->dMag);
+    }
+    fclose (f);
+    return true;
+}
+
+bool psastroWriteTransform (psPlaneTransform *map) {
+
+    // dump initial values:
+    for (int i = 0; i < map->x->nX + 1; i++) {
+	for (int j = 0; j < map->x->nY + 1; j++) {
+	    if (map->x->coeffMask[i][j] & PS_POLY_MASK_SET) continue;
+	    psLogMsg ("psastro", 4, "x term %d,%d: %f +/- %f\n", i, j, map->x->coeff[i][j], map->x->coeffErr[i][j]);
+	}
+    }
+
+    for (int i = 0; i < map->y->nX + 1; i++) {
+	for (int j = 0; j < map->y->nY + 1; j++) {
+	    if (map->y->coeffMask[i][j] & PS_POLY_MASK_SET) continue;
+	    psLogMsg ("psastro", 4, "y term %d,%d: %f +/- %f\n", i, j, map->y->coeff[i][j], map->y->coeffErr[i][j]);
+	}
+    }
+    return true;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroUseModel.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroUseModel.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroUseModel.c	(revision 23321)
@@ -0,0 +1,152 @@
+/** @file psastroMosiacAstrom.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+# define NONLIN_TOL 0.001 ///< tolerance in pixels
+# define DEBUG 0
+
+/**
+ * Apply the generic astrometry model to this image.  This assumes the WCS i
+ * terms either do not exist or are invalid 
+ */
+bool psastroUseModel (pmConfig *config, psMetadata *recipe) {
+
+  bool status;
+
+  bool useModel = psMetadataLookupBool (&status, config->arguments, "PSASTRO.USE.MODEL");
+  if (!status) {
+      useModel = psMetadataLookupBool (&status, recipe, "PSASTRO.USE.MODEL");
+  }
+  if (!useModel) return true;
+
+  // identify reference astrometry table.  
+  // if not defined, correction was not requested; skip step
+  pmFPAfile *astrom = psMetadataLookupPtr (NULL, config->files, "PSASTRO.MODEL");
+  if (!astrom) psAbort ("programming error: model was not supplied, though requested");
+
+  // select the input data sources
+  pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+  if (!input) psAbort ("programming error: no input data");
+
+  // make sure the astrometry model is loaded.  de-activate all files except PSASTRO.MODEL.
+  pmFPAfileActivate (config->files, false, NULL);
+  pmFPAfileActivate (config->files, true, "PSASTRO.MODEL");
+
+  pmFPAview *view = pmFPAviewAlloc (0);
+
+  // files associated with the science image
+  if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) {
+      psError (PS_ERR_IO, false, "Can't load the astrometry model file");
+      return false;
+  }
+
+  // XXX TEST: apply the input image RA & DEC to the astrometry model, save corners
+  if (0) {
+      // these externally supplied values are used to set the final transformation terms
+      double RA  = psMetadataLookupF64 (&status, input->fpa->concepts, "FPA.RA");
+      double DEC = psMetadataLookupF64 (&status, input->fpa->concepts, "FPA.DEC");
+      // double POS = PM_RAD_DEG * psMetadataLookupF64 (&status, input->fpa->concepts, "FPA.POSANGLE");
+
+      // get projection scale; center is supplied
+      // XXX should this be astrom or input??
+      float Xs = psMetadataLookupF32(&status, astrom->fpa->concepts, "XSCALE") * PM_RAD_DEG;
+      float Ys = psMetadataLookupF32(&status, astrom->fpa->concepts, "YSCALE") * PM_RAD_DEG;
+
+      // allocate a new toSky projection using the reported position
+      psFree (astrom->fpa->toSky);
+      astrom->fpa->toSky = psProjectionAlloc (RA, DEC, Xs, Ys, PS_PROJ_DIS);
+
+      // local view
+      pmFPAview *myView = pmFPAviewAlloc (0);
+
+      // loop over all chips, replace input astrometry elements with those from astrom 
+      pmChip *obsChip = NULL;
+      while ((obsChip = pmFPAviewNextChip (myView, input->fpa, 1)) != NULL) {
+	  psTrace ("psastro", 4, "Chip %d: %x %x\n", myView->chip, obsChip->file_exists, obsChip->process);
+	  if (!obsChip->process || !obsChip->file_exists || !obsChip->data_exists) { continue; }
+
+	  // set the chip astrometry using the astrom file
+	  pmChip *refChip = pmFPAviewThisChip (myView, astrom->fpa);
+
+	  psFree (obsChip->toFPA);
+	  psFree (obsChip->fromFPA);
+
+	  // supply astrometry from model 
+	  obsChip->toFPA   = psMemIncrRefCounter (refChip->toFPA);
+	  obsChip->fromFPA = psMemIncrRefCounter (refChip->fromFPA);
+
+	  // XXX if we want to write out the result, update the header here.  this needs to be
+	  // updated with the correct HDU selection.  obsChip->hdu may not exist.
+	  // pmAstromWriteBilevelChip (obsChip->hdu->header, obsChip, NONLIN_TOL);
+      }
+
+      psFree (input->fpa->toSky);
+      psFree (input->fpa->toTPA);
+      psFree (input->fpa->fromTPA);
+      input->fpa->toSky   = psMemIncrRefCounter (astrom->fpa->toSky);
+      input->fpa->toTPA   = psMemIncrRefCounter (astrom->fpa->toTPA);
+      input->fpa->fromTPA = psMemIncrRefCounter (astrom->fpa->fromTPA);
+
+      // XXX this is temporarily hardwired because of model error
+      input->fpa->toSky->type = PS_PROJ_TAN;
+
+      if (DEBUG) psastroDumpCorners ("corners.up.ast1.dat", "corners.dn.ast1.dat", input->fpa);
+  }
+
+  // set the model using the RA, DEC, POSANGLE of the input image.
+  pmAstromModelSetTP (astrom, input->fpa->concepts);
+
+  if (DEBUG) psastroDumpCorners ("corners.up.ast2.dat", "corners.dn.ast2.dat", astrom->fpa);
+
+  // loop over all chips, replace input astrometry elements with those from astrom 
+  pmChip *obsChip = NULL;
+  while ((obsChip = pmFPAviewNextChip (view, input->fpa, 1)) != NULL) {
+    psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, obsChip->file_exists, obsChip->process);
+    if (!obsChip->process || !obsChip->file_exists || !obsChip->data_exists) { continue; }
+
+    // set the chip astrometry using the astrom file
+    pmChip *refChip = pmFPAviewThisChip (view, astrom->fpa);
+
+    psFree (obsChip->toFPA);
+    psFree (obsChip->fromFPA);
+
+    // supply astrometry from model 
+    obsChip->toFPA   = psMemIncrRefCounter (refChip->toFPA);
+    obsChip->fromFPA = psMemIncrRefCounter (refChip->fromFPA);
+
+    // XXX if we want to write out the result, update the header here.  this needs to be
+    // updated with the correct HDU selection.  obsChip->hdu may not exist.
+    // pmAstromWriteBilevelChip (obsChip->hdu->header, obsChip, NONLIN_TOL);
+  }
+
+  psFree (input->fpa->toSky);
+  psFree (input->fpa->toTPA);
+  psFree (input->fpa->fromTPA);
+  input->fpa->toSky   = psMemIncrRefCounter (astrom->fpa->toSky);
+  input->fpa->toTPA   = psMemIncrRefCounter (astrom->fpa->toTPA);
+  input->fpa->fromTPA = psMemIncrRefCounter (astrom->fpa->fromTPA);
+
+  // XXX this is temporarily hardwired because of model error
+  input->fpa->toSky->type = PS_PROJ_TAN;
+
+  if (DEBUG) psastroDumpCorners ("corners.up.inp.dat", "corners.dn.inp.dat", input->fpa);
+
+  // updated with the correct HDU selection.  obsChip->hdu may not exist.
+  // psMetadata *updates = psMetadataAlloc();
+  // pmAstromWriteBilevelMosaic (updates, input->fpa, NONLIN_TOL);
+  // psMetadataAddMetadata (input->fpa->analysis, PS_LIST_TAIL, "PSASTRO.HEADER",  PS_META_REPLACE, "psastro header stats", updates);
+  // psFree (updates);
+
+  psFree (view);
+  return true;
+}
+
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroUtils.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroUtils.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroUtils.c	(revision 23321)
@@ -0,0 +1,244 @@
+/** @file psastroUtils.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.25 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-09 21:25:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+# define RENORM 0
+
+// fix this to look up the value in the chip concepts
+static double getChipPixelScale (pmChip *chip) {
+    return 10.0;
+}
+
+// I have an FPA structure with multiple chips.  we have loaded or measured astrometry for each
+// chip with independent pixel/degree scaling values.  These will naturally compensate locally
+// somewhat for the telescope distortion.  to measure the telescope distortion, we need to
+// force the chips to have the same pixel scale and measure the difference from that solution.
+// Convert an FPA with disparate pixel scales to a common pixel scale (perhaps depending on the
+// chip -- eg, TC3)
+
+bool psastroMosaicCommonScale (pmFPA *fpa, psMetadata *recipe) {
+
+    // options : use the MIN or MAX chip as global reference or supplied pixel scales
+    // (microns/pixel), which may depend on the chip
+
+    float pixelScaleUse = 1.0, pixelScale1 = 1.0,  pixelScale2 = 1.0,  pixelScale = 1.0;
+    psVector *oldScale = psVectorAllocEmpty (fpa->chips->n, PS_TYPE_F32);
+
+    char *option = psMetadataLookupStr (NULL, recipe, "PSASTRO.COMMON.SCALE.OPTION");
+    if (option == NULL) {
+        psError(PSASTRO_ERR_DATA, false, "no choice set for common scale option\n");
+        return false;
+    }
+
+    bool useExternal = true;
+    int nobj = 0;
+
+    // find the min or max scale chip
+    if (!strcasecmp (option, "MIN") || !strcasecmp (option, "MAX")) {
+
+        bool useMax = !strcasecmp (option, "MAX");
+        pixelScaleUse = (useMax) ? FLT_MIN : FLT_MAX;
+
+        for (int i = 0; i < fpa->chips->n; i++) {
+            pmChip *chip = fpa->chips->data[i];
+            if (!chip->process || !chip->file_exists) { continue; }
+            if (!chip->toFPA) { continue; }
+
+            if (chip->cells->n == 0) { continue; }
+            pmCell *cell = chip->cells->data[0];
+            if (!cell->process || !cell->file_exists) { continue; }
+
+            if (cell->readouts->n == 0) { continue; }
+            pmReadout *readout = cell->readouts->data[0];
+            if (! readout->data_exists) { continue; }
+
+            pixelScale1 = hypot (chip->toFPA->x->coeff[1][0], chip->toFPA->x->coeff[0][1]);
+            pixelScale2 = hypot (chip->toFPA->y->coeff[1][0], chip->toFPA->y->coeff[0][1]);
+            pixelScale = 0.5*(pixelScale1 + pixelScale2);
+            oldScale->data.F32[nobj++] = pixelScale;
+            pixelScaleUse = (useMax) ? PS_MAX (pixelScale, pixelScaleUse) : PS_MIN (pixelScale, pixelScaleUse);
+        }
+        useExternal = false;
+        oldScale->n = nobj;
+    }
+
+
+    // rescale each chip by the reference scale
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        if (!chip->process || !chip->file_exists) { continue; }
+        if (!chip->toFPA) { continue; }
+
+        psPlaneTransform *toFPA = chip->toFPA;
+        psPlaneTransform *fromFPA = chip->fromFPA;
+
+        pixelScale1 = hypot (toFPA->x->coeff[1][0], toFPA->x->coeff[0][1]);
+        pixelScale2 = hypot (toFPA->y->coeff[1][0], toFPA->y->coeff[0][1]);
+
+        if (useExternal) {
+            pixelScaleUse = getChipPixelScale (chip);
+        }
+
+        for (int i = 0; i <= toFPA->x->nX; i++) {
+            for (int j = 0; j <= toFPA->x->nX; j++) {
+                toFPA->x->coeff[i][j] *= pixelScaleUse/pixelScale1;
+                toFPA->y->coeff[i][j] *= pixelScaleUse/pixelScale2;
+                fromFPA->x->coeff[i][j] *= pixelScale1/pixelScaleUse;
+                fromFPA->y->coeff[i][j] *= pixelScale2/pixelScaleUse;
+            }
+        }
+    }
+    psastroMosaicSetAstrom (fpa);
+    if (!useExternal) {
+        pmAstromVisualPlotCommonScale (fpa, oldScale);
+    }
+    psFree (oldScale);
+    return true;
+}
+
+bool psastroUpdateChipToFPA (pmFPA *fpa, pmChip *chip, psArray *rawstars, psArray *refstars) {
+
+    psRegion *region = pmChipPixels (chip);
+
+    psFree (chip->fromFPA);
+    chip->fromFPA = psPlaneTransformInvert (NULL, chip->toFPA, *region, 50);
+    psFree (region);
+
+    for (int i = 0; i < rawstars->n; i++) {
+        pmAstromObj *raw = rawstars->data[i];
+
+        psPlaneTransformApply (raw->FP, chip->toFPA, raw->chip);
+        psPlaneTransformApply (raw->TP, fpa->toTPA, raw->FP);
+        psDeproject (raw->sky, raw->TP, fpa->toSky);
+    }
+
+    for (int i = 0; i < refstars->n; i++) {
+        pmAstromObj *ref = refstars->data[i];
+        psPlaneTransformApply (ref->chip, chip->fromFPA, ref->FP);
+    }
+
+    return true;
+}
+
+# if 0
+
+bool psastroSelectBrightStars (pmFPA *fpa, psMetadata *config) {
+
+    bool status;
+
+    // add exclusions for objects on some basis?
+    int MAX_NSTARS = psMetadataLookupS32 (&status, config, "PSASTRO.STARS.MAX");
+
+    for (int i = 0; i < fpa->chips->n; i++) {
+        pmChip *chip = fpa->chips->data[i];
+        for (int j = 0; j < chip->cells->n; j++) {
+            pmCell *cell = chip->cells->data[j];
+            for (int k = 0; k < cell->readouts->n; k++) {
+                pmReadout *readout = cell->readouts->data[k];
+
+                psArray *stars = psMetadataLookupPtr (&status, readout->analysis, "STARS.FULLSET");
+                stars = psArraySort (stars, pmAstromObjSortByMag);
+
+                int nSubset = PS_MIN (MAX_NSTARS, stars->n);
+                psArray *subset = psArrayAlloc (nSubset);
+
+                for (int i = 0; i < nSubset; i++) {
+                    subset->data[i] = stars->data[i];
+                }
+                psMetadataAdd (readout->analysis, PS_LIST_TAIL, "STARS.SUBSET", PS_DATA_ARRAY, "stars from analysis", subset);
+            }
+        }
+    }
+    return true;
+}
+
+psPlaneDistort *psPlaneDistortCopy (psPlaneDistort *input) {
+
+    psPlaneDistort *output = psPlaneDistortAlloc (input->x->nX, input->x->nY, input->x->nZ, input->x->nT);
+
+    for (int i = 0; i < input->x->nX; i++) {
+        for (int j = 0; j < input->x->nY; j++) {
+            for (int k = 0; k < input->x->nZ; k++) {
+                for (int m = 0; m < input->x->nT; m++) {
+                    // x-terms
+                    output->x->mask[i][j][k][m]     = input->x->mask[i][j][k][m];
+                    output->x->coeff[i][j][k][m]    = input->x->coeff[i][j][k][m];
+                    output->x->coeffErr[i][j][k][m] = input->x->coeffErr[i][j][k][m];
+                    // y-terms
+                    output->y->mask[i][j][k][m]     = input->y->mask[i][j][k][m];
+                    output->y->coeff[i][j][k][m]    = input->y->coeff[i][j][k][m];
+                    output->y->coeffErr[i][j][k][m] = input->y->coeffErr[i][j][k][m];
+                }
+            }
+        }
+    }
+    return (output);
+}
+
+psPlaneTransform *psPlaneTransformCopy (psPlaneTransform *input) {
+
+    psPlaneTransform *output = psPlaneTransformAlloc (input->x->nX, input->x->nY);
+
+    for (int i = 0; i < input->x->nX; i++) {
+        for (int j = 0; j < input->x->nY; j++) {
+            // x-terms
+            output->x->mask[i][j]     = input->x->mask[i][j];
+            output->x->coeff[i][j]    = input->x->coeff[i][j];
+            output->x->coeffErr[i][j] = input->x->coeffErr[i][j];
+            // y-terms
+            output->y->mask[i][j]     = input->y->mask[i][j];
+            output->y->coeff[i][j]    = input->y->coeff[i][j];
+            output->y->coeffErr[i][j] = input->y->coeffErr[i][j];
+        }
+    }
+    return (output);
+}
+
+psProjection *psProjectionCopy (psProjection *input) {
+
+    psProjection *output = psProjectionAlloc (input->R, input->D, input->Xs, input->Ys, input->type);
+    return (output);
+}
+
+// returns the rotation term, forcing positive parity
+double psPlaneTransformGetRotation (psPlaneTransform *map) {
+
+    if (map->x->nX < 1) return 0;
+    if (map->x->nY < 1) return 0;
+
+    if (map->y->nX < 1) return 0;
+    if (map->y->nY < 1) return 0;
+
+    double pc1_1 = map->x->coeff[1][0];
+    double pc1_2 = map->x->coeff[0][1];
+    double pc2_1 = map->y->coeff[1][0];
+    double pc2_2 = map->y->coeff[0][1];
+
+    double px = SIGN (pc1_1);
+    double py = SIGN (pc2_2);
+
+    // both x and y terms imply an angle. take the average
+    double t1 = -atan2 (px*pc1_2, px*pc1_1);
+    double t2 = +atan2 (py*pc2_1, py*pc2_2);
+
+    // careful near -pi,+pi boundary...
+    if (t1 - t2 > M_PI/2) t2 += 2*M_PI;
+    if (t2 - t1 > M_PI/2) t1 += 2*M_PI;
+
+    double theta = 0.5*(t1 + t2);
+    while (theta < M_PI) theta += 2*M_PI;
+    while (theta > M_PI) theta -= 2*M_PI;
+
+    return (theta);
+}
+
+# endif
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroVersion.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroVersion.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroVersion.c	(revision 23321)
@@ -0,0 +1,119 @@
+/** @file psastroVersion.c
+ *
+ *  @brief
+ *
+ *  @ingroup libpsastro
+ *
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "psastroInternal.h"
+
+#ifndef PSASTRO_VERSION
+#error "PSASTRO_VERSION is not set"
+#endif
+#ifndef PSASTRO_BRANCH
+#error "PSASTRO_BRANCH is not set"
+#endif
+#ifndef PSASTRO_SOURCE
+#error "PSASTRO_SOURCE is not set"
+#endif
+
+#define xstr(s) str(s)
+#define str(s) #s
+
+psString psastroVersion(void)
+{
+    char *value = NULL;
+    psStringAppend(&value, "%s@%s", xstr(PSASTRO_BRANCH), xstr(PSASTRO_VERSION));
+    return value;
+}
+
+psString psastroSource(void)
+{
+  return psStringCopy(xstr(PSASTRO_SOURCE));
+}
+
+psString psastroVersionLong(void)
+{
+    psString version = psastroVersion();  // Version, to return
+    psString source = psastroSource();    // Source
+
+    psStringPrepend(&version, "psastro ");
+    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 psastroVersionHeader(psMetadata *header)
+{
+    PS_ASSERT_METADATA_NON_NULL(header, false);
+
+    psString version = psastroVersion(); // Software version
+    psString source = psastroSource();   // Software source
+
+    psStringPrepend(&version, "psastro version: ");
+    psStringPrepend(&source, "psastro source: ");
+
+    psMetadataAddStr(header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, version);
+    psMetadataAddStr(header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, source);
+
+    psFree(version);
+    psFree(source);
+
+    return true;
+}
+
+bool psastroVersionHeaderFull(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, "psastro at %s", timeString);
+    psFree(timeString);
+    psMetadataAddStr(header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, history);
+    psFree(history);
+
+    psLibVersionHeader(header);
+    psModulesVersionHeader(header);
+    ppStatsVersionHeader(header);
+    psastroVersionHeader(header);
+
+    return true;
+}
+
+void psastroVersionPrint(void)
+{
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psLogMsg("psastro", PS_LOG_INFO, "psastro at %s", timeString);
+    psFree(timeString);
+
+    psString pslib = psLibVersionLong();// psLib version
+    psString psmodules = psModulesVersionLong(); // psModules version
+    psString ppStats = ppStatsVersionLong(); // ppStats version
+    psString psastro = psastroVersionLong(); // psastro version
+
+    psLogMsg("psastro", PS_LOG_INFO, "%s", pslib);
+    psLogMsg("psastro", PS_LOG_INFO, "%s", psmodules);
+    psLogMsg("psastro", PS_LOG_INFO, "%s", ppStats);
+    psLogMsg("psastro", PS_LOG_INFO, "%s", psastro);
+
+    psFree(pslib);
+    psFree(psmodules);
+    psFree(ppStats);
+    psFree(psastro);
+
+    return;
+}
Index: /branches/eam_branches/eam_branch_20090312/psastro/src/psastroZeroPoint.c
===================================================================
--- /branches/eam_branches/eam_branch_20090312/psastro/src/psastroZeroPoint.c	(revision 23321)
+++ /branches/eam_branches/eam_branch_20090312/psastro/src/psastroZeroPoint.c	(revision 23321)
@@ -0,0 +1,191 @@
+/** @file psastroMosaicZeroPoint.c
+ *
+ *  @brief 
+ *
+ *  @ingroup libpsastro
+ *
+ *  @author IfA
+ *  @version $Revision: 1.5 $ $Name: not supported by cvs2svn $
+ *  @date $Date: 2009-02-07 02:03:34 $
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+# include "psastroInternal.h"
+
+bool psastroZeroPoint (pmConfig *config) {
+
+    float zeropt, exptime;
+    pmChip *chip = NULL;
+    pmCell *cell = NULL;
+    pmReadout *readout = NULL;
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, PSASTRO_RECIPE);
+    if (!recipe) {
+	psError(PSASTRO_ERR_CONFIG, false, "Can't find PSASTRO recipe!\n");
+	return false;
+    }
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "PSASTRO.INPUT");
+    if (!input) {
+	psError(PSASTRO_ERR_CONFIG, false, "Can't find input data!\n");
+	return false;
+    }
+    pmFPA *fpa = input->fpa;
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // given the existing per-chip astrometry, determine matches between raw and ref stars
+    // is this needed? yes, if we didn't do SingleChip astrometry first
+    if (!psastroMosaicSetMatch (fpa, recipe, 0)) {
+        psError(PSASTRO_ERR_UNKNOWN, false, "failed to match raw and ref stars for mosaic");
+        return false;
+    }
+
+    // XXX eventually: look up the photcode info from the recipe, use the ZEROPT and A_0 terms
+    // to determine transparency = Mref - Mraw - 2.5*log10(exptime) - ZEROPT - A_0*Color_ref
+    // to get there, we need: a) getstar to write out the color-term magnitudes and b)  the
+    // pmAstromObj to carry a color value as well.
+
+    // really error-out here?  or just skip?
+    if (!psastroZeroPointFromRecipe (&zeropt, &exptime, fpa, recipe)) {
+	psLogMsg ("psastro", PS_LOG_INFO, "failed to load zeropt data from recipe");
+	return false;
+    }
+
+    // this loop selects the matched stars for all chips
+    while ((chip = pmFPAviewNextChip (view, fpa, 1)) != NULL) {
+        psTrace ("psastro", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+        if (!chip->process || !chip->file_exists) { continue; }
+	if (!chip->toFPA) { continue; }
+
+	while ((cell = pmFPAviewNextCell (view, fpa, 1)) != NULL) {
+            psTrace ("psastro", 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
+	    // XXX there can only be one readout per chip, right?
+	    while ((readout = pmFPAviewNextReadout (view, fpa, 1)) != NULL) {
+		if (! readout->data_exists) { continue; }
+
+		// calculate dMag for the matched stars
+		psastroZeroPointReadout (readout, zeropt, exptime);
+
+	    }
+	}
+    }
+
+    psFree (view);
+    return true;
+}
+
+/**
+ * we measure <dMag> and \sigma_dMag and write them to the header
+ */
+bool psastroZeroPointReadout(pmReadout *readout, float zeropt, float exptime) {
+
+    bool status;
+
+    // select the raw objects for this readout
+    psArray *rawstars = psMetadataLookupPtr (&status, readout->analysis, "PSASTRO.RAWSTARS");
+    if (rawstars == NULL) return false;
+
+    // select the raw objects for this readout
+    psArray *refstars = psMetadataLookupPtr (&status, readout->analysis, "PSASTRO.REFSTARS");
+    if (refstars == NULL) return false;
+
+    psArray *matches = psMetadataLookupPtr (&status, readout->analysis, "PSASTRO.MATCH");
+    if (matches == NULL) return false;
+
+    psVector *dMag  = psVectorAllocEmpty (100, PS_TYPE_F32);
+
+    int Npts = 0;
+    for (int i = 0; i < matches->n; i++) {
+
+      pmAstromMatch *match = matches->data[i];
+
+      pmAstromObj *raw = rawstars->data[match->raw];
+
+      pmAstromObj *ref = refstars->data[match->ref];
+
+      dMag->data.F32[Npts] = ref->Mag - raw->Mag - 2.5*log10(exptime);
+      psVectorExtend (dMag, 100, 1);
+      Npts++;
+    }
+
+    psTrace ("psModules.astrom", 4, "Npts: %d\n", Npts);
+
+    if (Npts < 3) {
+      fprintf (stderr, "zero point NaN +/- NaN\n");
+      psFree (dMag);
+      return false;
+    }
+
+    // stats structure and mask for use in measuring the clipping statistic
+    // this analysis has too few data points to use the robust median method
+    psStats *stats = psStatsAlloc (PS_STAT_CLIPPED_MEAN | PS_STAT_CLIPPED_STDEV);
+    psVectorStats (stats, dMag, NULL, NULL, 0);
+    fprintf (stderr, "zero point %f +/- %f using %d stars; transparency %f\n", stats->clippedMean, stats->clippedStdev, Npts, zeropt - stats->clippedMean);
+
+    psMetadata *header = psMetadataLookupMetadata (&status, readout->analysis, "PSASTRO.HEADER");
+
+    psMetadataAddF32 (header, PS_LIST_TAIL, "ZPT_OBS", PS_META_REPLACE, "measured zero point", stats->clippedMean);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "ZPT_REF", PS_META_REPLACE, "measured zero point", zeropt);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "ZPT_ERR", PS_META_REPLACE, "measured zero point", stats->clippedStdev);
+    psMetadataAddF32 (header, PS_LIST_TAIL, "ZPT_OFF", PS_META_REPLACE, "measured zero point", zeropt - stats->clippedMean);
+
+    psFree (dMag);
+    psFree (stats);
+    return true;
+}
+
+# define ESCAPE(MSG) { \
+  psLogMsg ("psastro", PS_LOG_INFO, MSG); \
+  return false; }
+
+bool psastroZeroPointFromRecipe (float *zeropt, float *exptime, pmFPA *fpa, psMetadata *recipe) {
+
+    bool status;
+
+    // select the filter; default to fixed photcode and mag limit otherwise
+    char *filter = psMetadataLookupStr (&status, fpa->concepts, "FPA.FILTERID");
+    if (!status) ESCAPE ("missing FPA.FILTER in concepts");
+
+    *exptime = psMetadataLookupF32 (&status, fpa->concepts, "FPA.EXPOSURE");
+    if (!status) ESCAPE ("missing FPA.EXPOSURE in concepts");
+
+    // we need to select the PHOTCODE.DATA folder that matches our filter
+    psMetadataItem *item = psMetadataLookup (recipe, "PHOTCODE.DATA");
+    if (!item) ESCAPE ("PHOTCODE.DATA folders missing");
+    if (item->type != PS_DATA_METADATA_MULTI) ESCAPE ("PHOTCODE.DATA not a multi");
+
+    // PHOTCODE.DATA is a multi of metadata items 
+    psListIterator *iter = psListIteratorAlloc(item->data.list, PS_LIST_HEAD, false);
+
+    psMetadataItem *refItem = NULL;
+    while ((refItem = psListGetAndIncrement (iter))) {
+	if (refItem->type != PS_DATA_METADATA) ESCAPE ("PHOTCODE.DATA entry is not a metadata folder");
+    
+	char *refFilter = psMetadataLookupStr (&status, refItem->data.md, "FILTER");
+	if (!status) {
+	    psLogMsg ("psastro", PS_LOG_INFO, "a PHOTCODE.DATA recipe folder is missing FILTER");
+	    continue;
+	}
+
+	// does this entry match the current filter?
+	if (strcmp (refFilter, filter)) continue;
+
+	psLogMsg ("psastro", PS_LOG_DETAIL, "PHOTCODE.DATA found for filter %s", filter);
+
+	*zeropt = psMetadataLookupF32 (&status, refItem->data.md, "ZEROPT");
+	if (!status) {
+	    psLogMsg ("psastro", PS_LOG_INFO, "a PHOTCODE.DATA recipe folder is missing FILTER");
+	    continue;
+	}
+	psFree (iter);
+	return true;
+    }
+    psFree (iter);
+    return false;
+}
