Index: /branches/ccl_branches/ipponly-20191108/magic/Makefile
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/Makefile	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/Makefile	(revision 41117)
@@ -0,0 +1,91 @@
+
+MAGIC_DIR = ~/magic_tarballs
+
+# necessary support for libQt (only used by magic)
+QTDIR = qt-everywhere-opensource-src-4.6.1
+QTSRC = $(MAGIC_DIR)/qt-everywhere-opensource-src-4.6.1.tar.gz
+QTDEP := $(shell test -f $(QTSRC) && echo $(QTSRC))
+
+PSCFG = ../psconfig/psconfig.csh
+PSPREFIX := $(shell csh $(PSCFG) $(PSVERSION) --prefix)
+
+default: all
+
+all: ssa-core-cpp magic verify
+
+install: magic.install verify.install
+
+clean:
+	if [ -d magic ]; then (cd magic && make -f Makefile.magic clean); fi
+	# if [ -d ssa-core-cpp ]; then (cd ssa-core-cpp && make clean); fi
+
+update:
+# get the source code (replace this with SVN interactions)
+	tar xvzf $(MAGIC_DIR)/magic.tgz
+	tar xvzf $(MAGIC_DIR)/ssa-core-cpp.tgz
+	cp Makefile.magic magic/Makefile.magic
+	cp magic/VerifyStreaks/main.cpp verify/VerifyStreaks.cpp
+
+# tar xvzf $(QTSRC)
+
+qt-everywhere: $(QTDIR)/lib/libQtCore.so
+
+QTOPT = -fast \
+	-opensource \
+	-nomake tools \
+	-nomake examples \
+	-nomake demos \
+	-nomake docs \
+	-nomake translations \
+	-no-xmlpatterns \
+	-no-multimedia \
+	-no-audio-backend \
+	-no-phonon \
+	-no-phonon-backend \
+	-no-svg \
+	-no-webkit \
+	-no-javascript-jit \
+	-no-script \
+	-no-scripttools 
+
+# the target (QTDEP) is blank unless QTSRC exists
+$(QTDIR)/lib/libQtCore.so: $(QTDEP)
+	echo $(PSCFG)
+	echo $(PSPREFIX)
+	if [ -d $(QTDIR) ]; then (cd $(QTDIR) && echo "yes" | ./configure $(QTOPT) -prefix $(PSPREFIX)); fi
+	if [ -d $(QTDIR) ]; then (cd $(QTDIR) && make); fi
+	if [ -d $(QTDIR) ]; then (cd $(QTDIR) && make install); fi
+
+ssa-core-cpp: ssa-core-cpp/build/libSSA.a
+
+ssa-core-cpp/build/libSSA.a:
+	if [ -d ssa-core-cpp ]; then (cd ssa-core-cpp && ./configure --disable-doxygen && make); fi
+
+magic: magic/DetectStreaks
+
+magic/DetectStreaks:
+	if [ -d magic ]; then (cd magic && make -f Makefile.magic); fi
+
+magic.install:	ssa-core-cpp
+	if [ -d magic ]; then (cd magic && make -f Makefile.magic install); fi
+
+update.verify:
+	cp magic/VerifyStreaks/main.cpp verify/VerifyStreaks.cpp
+
+verify: verify/VerifyStreaks
+
+verify/VerifyStreaks:
+	if [ -d magic ]; then (cd verify && make); fi
+
+verify.install:
+	if [ -d magic ]; then (cd verify && make install); fi
+
+# REMOVE = remove/src
+# 
+# remove: FORCE
+# 	@cd $(REMOVE) && make -f Makefile.simple all
+# 
+# remove.install: remove
+# 	@cd $(REMOVE) && make -f Makefile.simple install
+
+FORCE:
Index: /branches/ccl_branches/ipponly-20191108/magic/Makefile.magic
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/Makefile.magic	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/Makefile.magic	(revision 41117)
@@ -0,0 +1,38 @@
+TARGETS =  DetectStreaks
+
+CXX = c++
+CXXFLAGS = -W -Wall -O3 -DNDEBUG
+# CXXFLAGS = -W -Wall -g
+
+SSA_SRC_DIR = ../ssa-core-cpp/src
+INCLUDEDIRS = -I. -I$(SSA_SRC_DIR)
+OTHER_SRCS = $(SSA_SRC_DIR)/math/Constants.cpp
+
+# CFITSIO_DIR = ../cfitsio
+CFITSIO_DIR = $(PSCONFDIR)/$(PSCONFIG)
+
+CFITSIO_INC =  -I$(CFITSIO_DIR)
+CFITSIO_LIB = -L$(CFITSIO_DIR) -lcfitsio
+
+all : $(TARGETS)
+
+RemoveStreaks : $(OTHER_SRCS)
+
+DetectStats : $(OTHER_SRCS)
+
+% :: %.cpp
+	$(CXX) $(CXXFLAGS) $(INCLUDEDIRS) $(CFITSIO_INC) $< $(OTHER_SRCS) $(CFITSIO_LIB) -o $@
+
+.PHONY : clean
+
+.SILENT : clean
+
+clean :
+	   - rm $(TARGETS)
+
+DESTDIR=$(PSCONFDIR)/$(PSCONFIG)
+
+install:        $(TARGETS)
+	cp DetectStreaks $(DESTDIR)/bin
+	chmod 755  $(DESTDIR)/bin/DetectStreaks
+
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/Doxyfile.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/Doxyfile.in	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/Doxyfile.in	(revision 41117)
@@ -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           = censor
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER         = ipp-2.7.dev
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = ./docs
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of 
+# source files, where putting all generated files in the same directory would 
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, 
+# Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hungarian, 
+# Italian, Japanese, Japanese-en (Japanese with English messages), Korean, 
+# Korean-en, Lithuanian, Norwegian, Polish, Portuguese, Romanian, Russian, 
+# Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is 
+# used as the annotated text. Otherwise, the brief description is used as-is. 
+# If left blank, the following values are used ("$name" is automatically 
+# replaced with the name of the entity): "The $name class" "The $name widget" 
+# "The $name file" "is" "provides" "specifies" "contains" 
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
+# inherited members of a class in the documentation of that class as if those 
+# members were ordinary class members. Constructors, destructors and assignment 
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful is your file systems 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like regular Qt-style comments 
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will 
+# interpret the first line (until the first dot) of a Qt-style 
+# comment as the brief description. If set to NO, the comments 
+# will behave just like regular Qt-style comments (thus requiring 
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen 
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member 
+# documentation.
+
+DETAILS_AT_TOP         = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
+# a new page for each member. If set to NO, the documentation of a member will 
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
+# sources only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java 
+# sources only. Doxygen will then generate output that is more tailored for Java. 
+# For instance, namespaces will be presented as packages, qualified scopes 
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to 
+# include (a tag file for) the STL sources as input, then you should 
+# set this tag to YES in order to let doxygen match functions declarations and 
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. 
+# func(std::string) {}). This also make the inheritance and collaboration 
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. 
+# Doxygen will parse them like normal C++ but will assume all classes use public 
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct (or union) is 
+# documented as struct with the name of the typedef. So 
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct 
+# with name TypeT. When disabled the typedef will appear as a member of a file, 
+# namespace, or class. And the struct will be named TypeS. This can typically 
+# be useful for C code where the coding convention is that all structs are 
+# typedef'ed and only the typedef is referenced never the struct's name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local 
+# methods, which are defined in the implementation section but not in 
+# the interface are included in the documentation. 
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be extracted 
+# and appear in the documentation as a namespace called 'anonymous_namespace{file}', 
+# where file will be replaced with the base name of the file that contains the anonymous 
+# namespace. By default anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories 
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy 
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
+# doxygen should invoke to get the current version for each file (typically from the 
+# version control system). Doxygen will invoke the program by executing (via 
+# popen()) the command <command> <input-file>, where <command> is the value of 
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
+# provided by doxygen. Whatever the program writes to standard output 
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for 
+# functions that are documented, but have no documentation for their parameters 
+# or return value. If set to NO (the default) doxygen will only warn about 
+# wrong or incomplete parameter documentation, but not about the absence of 
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text. Optionally the format may contain 
+# $version, which will be replaced by the version of the file (if it could 
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text "
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT                  = ./src
+
+# This tag can be used to specify the character encoding of the source files that 
+# doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default 
+# input encoding. Doxygen uses libiconv (or the iconv built into libc) for the transcoding. 
+# See http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx 
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS          = *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                = 
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
+# directories that are symbolic links (a Unix filesystem feature) are excluded 
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories. Note that the wildcards are matched 
+# against the file with absolute path, so to exclude all test directories 
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       = 
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names 
+# (namespaces, classes, functions, etc.) that should be excluded from the output. 
+# The symbol name can be a fully qualified name, a word, or if the wildcard * is used, 
+# a substring. Examples: ANamespace, AClass, AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           = 
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = 
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis.  Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match.  The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
+# is applied to all files.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO. If you have enabled CALL_GRAPH or CALLER_GRAPH 
+# then you must also enable this option. If you don't then doxygen will produce 
+# a warning and turn it on anyway
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default) 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default) 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.  Otherwise they will link to the documentstion.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code 
+# will point to the HTML generated by the htags(1) tool instead of doxygen 
+# built-in source browser. The htags tool is part of GNU's global source 
+# tagging system (see http://www.gnu.org/software/global/global.html). You 
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        = 
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
+# files or namespaces will be aligned in HTML using tables. If set to 
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML 
+# documentation will contain sections that can be hidden and shown after the 
+# page has loaded. For this to work a browser that supports 
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox 
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA             = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD                = 
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader.  This is useful 
+# if you want to understand what is going on.  On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor   
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED             = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse 
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#   TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#   TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
+# or super classes. Setting the tag to NO turns the diagrams off. Note that 
+# this option is superseded by the HAVE_DOT option below. This is only a 
+# fallback. It is recommended to install and use dot, since it yields more 
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc 
+# command. Doxygen will then run the mscgen tool (see http://www.mcternan.me.uk/mscgen/) to 
+# produce the chart and insert it in the documentation. The MSCGEN_PATH tag allows you to 
+# specify the directory where the mscgen tool resides. If left empty the tool is assumed to 
+# be found in the default search path.
+
+MSCGEN_PATH            = 
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will 
+# generate a call dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable call graphs for selected 
+# functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will 
+# generate a caller dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable caller graphs for selected 
+# functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES 
+# then doxygen will show the dependencies a directory has on other directories 
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of 
+# nodes that will be shown in the graph. If the number of nodes in a graph 
+# becomes larger than this value, doxygen will truncate the graph, which is 
+# visualized by representing a node as a red box. Note that doxygen if the number 
+# of direct children of the root node in a graph is already larger than 
+# MAX_DOT_GRAPH_NOTES then the graph will not be shown at all. Also note 
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes 
+# that lay further from the root node will be omitted. Note that setting this 
+# option to 1 or 2 may greatly reduce the computation time needed for large 
+# code bases. Also note that the size of a graph can be further restricted by 
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
+# background. This is disabled by default, which results in a white background. 
+# Warning: Depending on the platform used, enabling this option may lead to 
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to 
+# read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
+# files in one run (i.e. multiple -o and -T options on the command line). This 
+# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine   
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/Makefile.am
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/Makefile.am	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/Makefile.am	(revision 41117)
@@ -0,0 +1,4 @@
+SUBDIRS = src
+
+CLEANFILES = *.pyc *~ core core.*
+
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/autogen.sh
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/autogen.sh	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/autogen.sh	(revision 41117)
@@ -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=censor
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOLIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOLIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE  failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/censorObjects.pc.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/censorObjects.pc.in	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/censorObjects.pc.in	(revision 41117)
@@ -0,0 +1,12 @@
+prefix=@prefix@
+exec_prefix=@exec_prefix@
+libdir=@libdir@
+includedir=@includedir@
+
+Name: libcensor
+Description: Pan-STARRS Photometry Library
+Version: @VERSION@
+Requires: pslib psmodules
+Libs: -L${libdir} -lcensor
+Libs.private: @CENSOR_LIBS@
+Cflags: -I${includedir} @CENSOR_CFLAGS@
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/configure.ac
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/configure.ac	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/configure.ac	(revision 41117)
@@ -0,0 +1,49 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.61)
+
+AC_INIT([censorObjects], [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_STDLDFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC_C99
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE
+
+dnl ------------------------------------------------------------
+
+AC_PATH_PROG([ERRORCODES], [psParseErrorCodes], [missing])
+if test "$ERRORCODES" = "missing" ; then
+  AC_MSG_ERROR([psParseErrorCodes is required])
+fi
+
+dnl ------------- psLib, psModules ---------------
+PKG_CHECK_MODULES([PSLIB],    [pslib >= 1.0.0])
+PKG_CHECK_MODULES([PSMODULE], [psmodules >= 1.0.0])
+
+dnl Set CFLAGS for build
+IPP_STDOPTS
+IPP_STDCFLAGS
+
+echo "CENSOR_CFLAGS: $CENSOR_CFLAGS"
+echo "CENSOR_LIBS: $CENSOR_LIBS"
+
+IPP_VERSION
+
+AC_SUBST([CENSOR_CFLAGS])
+AC_SUBST([CENSOR_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  Doxyfile
+])
+
+AC_OUTPUT
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/Makefile.am
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/Makefile.am	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/Makefile.am	(revision 41117)
@@ -0,0 +1,43 @@
+
+# Force recompilation of censorVersion.c, since it gets the version information
+censorVersion.c: censorVersionDefinitions.h
+censorVersionDefinitions.h: censorVersionDefinitions.h.in FORCE
+	pslib-setsvnversion.pl CENSOR censorVersionDefinitions.h.in censorVersionDefinitions.h
+FORCE: ;
+
+bin_PROGRAMS = censorObjects
+
+censorObjects_CFLAGS = $(CENSOR_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS)
+censorObjects_LDFLAGS = $(CENSOR_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS)
+
+censorObjects_SOURCES = \
+	censor.c		    \
+        censorArguments.c	    \
+	censorParseCamera.c   	    \
+	censorLoop.c                \
+	censorReadout.c             \
+	censorErrorCodes.c          \
+	censorVersion.c             \
+	censorCleanup.c
+
+include_HEADERS = \
+	censor.h \
+	censorErrorCodes.h
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
+### Error codes.
+BUILT_SOURCES = censorErrorCodes.h censorErrorCodes.c censorVersionDefinitions.h
+CLEANFILES = censorErrorCodes.h censorErrorCodes.c
+EXTRA_DIST = censorErrorCodes.dat censorErrorCodes.h.in censorErrorCodes.c.in
+
+censorErrorCodes.h : censorErrorCodes.dat censorErrorCodes.h.in
+	$(ERRORCODES) --data=censorErrorCodes.dat --outdir=. censorErrorCodes.h
+
+censorErrorCodes.c : censorErrorCodes.dat censorErrorCodes.c.in censorErrorCodes.h
+	$(ERRORCODES) --data=censorErrorCodes.dat --outdir=. censorErrorCodes.c
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censor.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censor.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censor.c	(revision 41117)
@@ -0,0 +1,73 @@
+/** @file censor.c
+ *
+ *  @brief
+ *
+ *  @ingroup censor
+ *
+ *  @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 "censor.h"
+
+static void usage(void) {
+    fprintf(stderr, "USAGE: censorObjects -file (sources) -masklist (masklist) (output)\n");
+    fprintf(stderr, "       censorObjects -file (sources) -mask (mask.fits) (output)\n");
+    fprintf(stderr, "       censorObjects -list (sourceslist) -masklist (masklist) (output)\n");
+    fprintf(stderr, "       censorObjects -list (sourceslist) -mask (mask.fits) (output)\n");
+    exit(PS_EXIT_CONFIG_ERROR);
+}
+
+int main (int argc, char **argv)
+{
+    psTimerStart ("complete");
+
+    censorErrorRegister();              // register our error codes/messages
+
+    // model inits are needed in pmSourceIO
+    // models defined in psphot/src/models are not available in censor
+    pmModelClassInit();
+
+    // load configuration information
+    pmConfig *config = censorArguments(argc, argv);
+    if (!config) {
+        usage();
+    }
+
+    censorVersionPrint();
+
+    // load identify the data sources
+    if (!censorParseCamera (config)) {
+        psErrorStackPrint(stderr, "error setting up the camera\n");
+        psFree(config);
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    bool status;
+    bool checkInputOnly = psMetadataLookupBool(&status, config->arguments, 
+        "CHECK.INPUT.ONLY");
+
+    if (!checkInputOnly) {
+        // load the raw detection data (using PSPHOT.SOURCES filerule)
+        // select subset of stars for astrometry
+        if (!censorLoop (config)) {
+            psErrorStackPrint(stderr, "error loading input data\n");
+            psFree(config);
+            exit(PS_EXIT_DATA_ERROR);
+        }
+
+        psLogMsg("censor", 3, "complete censor run: %f sec\n", psTimerMark ("complete"));
+    } else {
+        if (!checkInputLoop (config)) {
+            psErrorStackPrint(stderr, "error loading input data\n");
+            psFree(config);
+            exit(PS_EXIT_DATA_ERROR);
+        }
+        psLogMsg("censor", 3, "complete check input run: %f sec\n", psTimerMark ("complete"));
+    }
+
+    censorCleanup(config);
+    exit(PS_EXIT_SUCCESS);
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censor.h
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censor.h	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censor.h	(revision 41117)
@@ -0,0 +1,47 @@
+/** @file censor.h
+ *
+ *  @brief censorObjects rejects objects from smf files that land on magic masks
+ *
+ *  @ingroup censor
+ *
+ *  @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 CENSOR_H
+# define CENSOR_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 "censorErrorCodes.h"
+
+/// @addtogroup censor
+/// @{
+
+# define CENSOR_RECIPE "CENSOR" ///< Name of the recipe to use
+
+pmConfig *censorArguments (int argc, char **argv);
+bool censorParseCamera (pmConfig *config);
+bool censorLoop (pmConfig *config);
+bool checkInputLoop (pmConfig *config);
+bool censorReadout (pmReadout *readout, pmConfig *config, const pmFPAview *view, bool badMaskFile);
+void censorCleanup (pmConfig *config);
+bool censorBadFileByView (bool badFile, pmFPAfile *file, pmConfig *config, pmFPAview *view);
+
+// Return version strings.
+psString          censorVersion(void);
+psString          censorSource(void);
+psString          censorVersionLong(void);
+bool              censorVersionHeader(psMetadata *header);
+bool              censorVersionHeaderFull(psMetadata *header);
+void              censorVersionPrint(void);
+
+///@}
+# endif /* CENSOR_H */
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorArguments.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorArguments.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorArguments.c	(revision 41117)
@@ -0,0 +1,69 @@
+/** @file censorArguments.c
+ *
+ *  @brief
+ *
+ *  @ingroup censor
+ *
+ *  @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 "censor.h"
+
+pmConfig *censorArguments (int argc, char **argv) {
+
+    bool status;
+    int N;
+
+    if (argc == 1) {
+        psError(CENSOR_ERR_ARGUMENTS, true, "No arguments supplied");
+        psErrorStackPrint(stderr, "exit");
+        return NULL;
+    }
+
+    // load config data from default locations
+    pmConfig *config = pmConfigRead(&argc, argv, CENSOR_RECIPE);
+    if (config == NULL) {
+        psError(CENSOR_ERR_CONFIG, false, "Can't read site configuration");
+        psErrorStackPrint(stderr, "exit");
+        return NULL;
+    }
+
+    status = pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT", "-file", "-list");
+    if (!status) {
+        psError(CENSOR_ERR_ARGUMENTS, true, "Missing -file (input) or -list (input)");
+        psErrorStackPrint(stderr, "exit");
+        return NULL;
+    }
+
+    if ((N = psArgumentGet (argc, argv, "-checkinputonly"))) {
+        psArgumentRemove (N, &argc, argv);
+        psMetadataAddBool(config->arguments, PS_LIST_TAIL, "CHECK.INPUT.ONLY", PS_META_REPLACE, "do not censor just check input sources", true);
+        return config;
+    }
+        
+    // define the input mask
+    status = pmConfigFileSetsMD (config->arguments, &argc, argv, "INPUT.MASK", "-mask", "-masklist");
+
+    // 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);
+    }
+
+
+    if (argc != 2) {
+        psError(CENSOR_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("censor", 1, "Done with censorArguments...\n");
+    return (config);
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorCleanup.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorCleanup.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorCleanup.c	(revision 41117)
@@ -0,0 +1,30 @@
+/** @file censorCleanup.c
+ *
+ *  @brief
+ *
+ *  @ingroup censor
+ *
+ *  @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 "censor.h"
+
+void censorCleanup (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), "censor");
+    // fprintf (stderr, "found %d leaks at %s\n", psMemCheckLeaks (0, NULL, NULL, false), "censor");
+
+    return;
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorErrorCodes.c.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorErrorCodes.c.in	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorErrorCodes.c.in	(revision 41117)
@@ -0,0 +1,37 @@
+/** @file censorErrorCodes.c
+ *
+ *  @brief
+ *
+ *  @ingroup censor
+ *
+ *  @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 "censorErrorCodes.h"
+
+/*
+ * The line
+    { CENSOR_ERR_$X{ErrorCode}, "$X{ErrorDescription}"},
+ * (without the Xs)
+ * will be replaced by values from errorCodes.dat
+ */
+void censorErrorRegister(void)
+{
+    static psErrorDescription errors[] = {
+       { CENSOR_ERR_BASE, "First value we use; lower values belong to psLib" },
+       { CENSOR_ERR_${ErrorCode}, "${ErrorDescription}"},
+    };
+    static int nerror = CENSOR_ERR_NERROR - CENSOR_ERR_BASE; // number of values in enum
+
+    for (int i = 0; i < nerror; i++) {
+       psErrorDescription *tmp = psAlloc(sizeof(psErrorDescription));
+       *tmp = errors[i];
+       psErrorRegister(tmp, 1);
+       psFree(tmp);			/* it's on the internal list */
+    }
+    nerror = 0;			                // don't register more than once
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorErrorCodes.dat
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorErrorCodes.dat	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorErrorCodes.dat	(revision 41117)
@@ -0,0 +1,12 @@
+#
+# This file is used to generate pmErrorClasses.h
+#
+BASE = 16000		First value we use
+UNKNOWN			Unknown censorObject 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/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorErrorCodes.h.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorErrorCodes.h.in	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorErrorCodes.h.in	(revision 41117)
@@ -0,0 +1,30 @@
+/** @file censorErrorCodes.h
+ *
+ *  @brief
+ *
+ *  @ingroup censor
+ *
+ *  @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(CENSOR_ERROR_CODES_H)
+#define CENSOR_ERROR_CODES_H
+/*
+ * The line
+ *  CENSOR_ERR_$X{ErrorCode},
+ * (without the X)
+ *
+ * will be replaced by values from errorCodes.dat
+ */
+typedef enum {
+    CENSOR_ERR_BASE = 16000,
+    CENSOR_ERR_${ErrorCode},
+    CENSOR_ERR_NERROR
+} censorErrorCode;
+
+void censorErrorRegister(void);
+
+#endif
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorLoop.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorLoop.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorLoop.c	(revision 41117)
@@ -0,0 +1,251 @@
+/** @file censorLoop.c
+ *
+ *  @ingroup censor
+ *
+ *  @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 "censor.h"
+
+# define ESCAPE { \
+  psError(PS_ERR_UNKNOWN, false, "Failure in censorLoop"); \
+  psFree (view); \
+  return false; \
+}
+  
+/**
+ * \brief this loop loads the data from the input smf file(s) and mask(s).  call censorReadout
+ * to perform the actual rejection.
+ */
+bool censorLoop (pmConfig *config) {
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    psTimerStart ("censor");
+
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (NULL, config->recipes, CENSOR_RECIPE);
+    if (!recipe) {
+	psError(CENSOR_ERR_CONFIG, true, "Can't find CENSOR recipe!\n");
+	return false;
+    }
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "CENSOR.INPUT");
+    if (!input) {
+	psError(CENSOR_ERR_CONFIG, true, "Can't find input data!\n");
+	return false;
+    }
+
+    // select the input data sources
+    pmFPAfile *inMask = psMetadataLookupPtr (NULL, config->files, "CENSOR.INPUT.MASK");
+    if (!inMask) {
+	psError(CENSOR_ERR_CONFIG, true, "Can't find input masks!\n");
+	return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    bool badMaskFile = false;
+
+    // check on the mask file at this level if possible
+    badMaskFile = censorBadFileByView(badMaskFile, inMask, config, view);
+
+    bool checkInputOnly = psMetadataLookupBool(NULL, config->arguments, "CHECK.INPUT.ONLY");
+
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+
+    // 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 ("censor", 4, "Chip %d: %x %x\n", view->chip, chip->file_exists, chip->process);
+	if (!chip->process || !chip->file_exists) { continue; }
+	
+	// check on the mask file at this level if possible
+	badMaskFile = censorBadFileByView(badMaskFile, inMask, config, view);
+
+	if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+
+	// read WCS data from the corresponding header
+
+	pmHDU *hdu = pmFPAviewThisHDU (view, input->fpa);
+	
+	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, 1.0)) {
+	    psWarning( "Unable to read WCS astrometry from chip %d, skipping", view->chip);
+	    continue;
+	  }
+	}
+
+	while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+	    psTrace ("censor", 4, "Cell %d: %x %x\n", view->cell, cell->file_exists, cell->process);
+	    if (!cell->process || !cell->file_exists) { continue; }
+
+	    // check on the mask file at this level if possible
+	    badMaskFile = censorBadFileByView(badMaskFile, inMask, config, view);
+
+	    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+
+	    // process each of the readouts
+	    while ((readout = pmFPAviewNextReadout (view, input->fpa, 1)) != NULL) {
+
+		// check on the mask file at this level if possible
+		badMaskFile = censorBadFileByView(badMaskFile, inMask, config, view);
+
+		if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+		if (!readout->data_exists) { continue; }
+
+                if (!checkInputOnly) {
+                    if (!censorReadout (readout, config, view, badMaskFile)) 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 ("censor", 3, "censorLoop done : %f sec\n", psTimerMark ("censor"));
+
+    psFree (view);
+    return true;
+}
+
+bool censorBadFileByView (bool badFile, pmFPAfile *file, pmConfig *config, pmFPAview *view) {
+
+    // get the current level
+    pmFPALevel level = pmFPAviewLevel (view);
+
+    // can we try to open this file? (if not, leave the existing badView intact)
+    if (level != file->fileLevel) return badFile;
+
+    // free, and if needed, realloc badView below
+    badFile = false;
+
+    char *filename = pmFPAfileName(file, view, config);
+    if (filename) {
+	// activate the file
+	pmFPAfileActivate (config->files, true, file->name);
+	psFree (filename);
+	return false;
+    }
+
+    // de-activate the file
+    pmFPAfileActivate (config->files, false, file->name);
+    return true;
+}
+
+/**
+ * \brief this loop loads the data from the input smf file(s) and mask(s).  call censorReadout
+ * to perform the actual rejection.
+ */
+bool checkInputLoop (pmConfig *config) {
+    pmChip *chip;
+    pmCell *cell;
+    pmReadout *readout;
+
+    psTimerStart ("checksources");
+
+    // select the input data sources
+    pmFPAfile *input = psMetadataLookupPtr (NULL, config->files, "CENSOR.INPUT");
+    if (!input) {
+	psError(CENSOR_ERR_CONFIG, true, "Can't find input data!\n");
+	return false;
+    }
+
+    pmFPAview *view = pmFPAviewAlloc (0);
+
+    // files associated with the science image
+    if (!pmFPAfileIOChecks (config, view, PM_FPA_BEFORE)) ESCAPE;
+
+    // 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 ("checksources", 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;
+
+	// read WCS data from the corresponding header
+
+	pmHDU *hdu = pmFPAviewThisHDU (view, input->fpa);
+	
+	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, 1.0)) {
+	    psWarning( "Unable to read WCS astrometry from chip %d, skipping", view->chip);
+	    continue;
+	  }
+	}
+
+	while ((cell = pmFPAviewNextCell (view, input->fpa, 1)) != NULL) {
+	    psTrace ("checksources", 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; }
+                
+                // select the raw objects for this readout, flag is they fall in a mask
+                pmDetections *detections = psMetadataLookupPtr (NULL, readout->analysis, "PSPHOT.DETECTIONS");
+                if (detections == NULL) {
+                    psError(CENSOR_ERR_CONFIG, true, "Can't find detections for this readout");
+                    return false;
+                }
+                if (!detections->allSources) {
+                    psError(CENSOR_ERR_CONFIG, true, "Can't find sources for this readout");
+                    return false;
+                }
+
+		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 ("checksources", 3, "checkInputLoop done : %f sec\n", psTimerMark ("checksources"));
+
+    psFree (view);
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorParseCamera.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorParseCamera.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorParseCamera.c	(revision 41117)
@@ -0,0 +1,57 @@
+/** @file censorParseCamera.c
+ *
+ *  @brief
+ *
+ *  @ingroup censor
+ *
+ *  @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 "censor.h"
+
+bool censorParseCamera (pmConfig *config) {
+
+    bool status = false;
+
+    // the input image(s) are required arguments; they define the camera
+    pmFPAfile *input = pmFPAfileDefineFromArgs (&status, config, "CENSOR.INPUT", "INPUT");
+    if (!status) {
+	psError(CENSOR_ERR_CONFIG, false, "Failed to build FPA from CENSOR.INPUT");
+	return false;
+    }
+
+    bool checkInputOnly = psMetadataLookupBool(&status, config->arguments, "CHECK.INPUT.ONLY");
+    if (checkInputOnly) {
+        // nothing else to do
+        return true;
+    }
+
+
+    pmFPAfile *output = pmFPAfileDefineOutput (config, input->fpa, "CENSOR.OUTPUT");
+    if (!output) {
+	psError(CENSOR_ERR_CONFIG, false, "Failed to build output FPA from CENSOR.INPUT");
+	return false;
+    }
+    output->save = true;
+
+    // look for the file on the argument list
+    pmFPAfile *inMask = pmFPAfileDefineFromArgs  (&status, config, "CENSOR.INPUT.MASK", "INPUT.MASK");
+    if (!status) {
+	psError (PS_ERR_UNKNOWN, false, "failed to load input mask definition");
+	return false;
+    }
+    if (!inMask) {
+	psError (PS_ERR_UNKNOWN, false, "failed to load input mask");
+	return false;
+    }
+    if (inMask->type != PM_FPA_FILE_MASK) {
+	psError(PS_ERR_IO, true, "CENSOR.INPUT.MASK is not of type MASK");
+	return false;
+    }
+
+    psTrace("censor", 1, "Done with censorParseCamera...\n");
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorReadout.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorReadout.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorReadout.c	(revision 41117)
@@ -0,0 +1,92 @@
+/** @file censorReadout.c
+ *
+ *  @brief reject detections that land within the streak mask regions
+ *
+ *  @ingroup censor
+ *
+ *  @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 "censor.h"
+
+bool censorReadout (pmReadout *readout, pmConfig *config, const pmFPAview *view, bool badMaskFile) {
+
+    bool status;
+
+    // if there is no mask file for this readout drop all sources
+    if (badMaskFile) {
+      psMetadataRemoveKey (readout->analysis, "PSPHOT.DETECTIONS");
+      return true;
+    }
+
+    // select the current recipe
+    psMetadata *recipe  = psMetadataLookupPtr (&status, config->recipes, CENSOR_RECIPE);
+    if (!recipe) {
+        psError(CENSOR_ERR_CONFIG, true, "Can't find CENSOR recipe");
+        return false;
+    }
+
+    psImageMaskType streakMaskValue = pmConfigMaskGet("STREAK", config); // Mask value for streak pixels
+
+    psLogMsg ("censor", PS_LOG_INFO, "generating a bright-star mask");
+
+    // select the reference mask fpa :: we use this to determine cell boundaries
+    pmFPAfile *inMask = psMetadataLookupPtr (NULL, config->files, "CENSOR.INPUT.MASK");
+    if (!inMask) {
+        psError(CENSOR_ERR_CONFIG, true, "Can't find mask files");
+        return false;
+    }
+
+    // XXX why not do this?
+    pmReadout *readoutMask = pmFPAviewThisReadout (view, inMask->fpa);
+    if (!readoutMask) {
+        psError(CENSOR_ERR_CONFIG, true, "Can't find mask for this readout");
+        return false;
+    }
+
+    // select the raw objects for this readout, flag is they fall in a mask
+    pmDetections *detections = psMetadataLookupPtr (&status, readout->analysis, "PSPHOT.DETECTIONS");
+    if (detections == NULL) {
+        psError(CENSOR_ERR_CONFIG, true, "Can't find detections for this readout");
+        return false;
+    }
+    psArray *inSources = detections->allSources;
+    psAssert (inSources, "missing sources?");
+
+    // create a replacement output array:
+    psArray *outSources = psArrayAllocEmpty(100);
+
+    // only keep sources that do not match the specified mask bit
+    for (int i = 0; i < inSources->n; i++) {
+        pmSource *source = inSources->data[i];
+
+        int xChip = source->peak->x;
+        int yChip = source->peak->y;
+
+        bool onChip = true;
+        onChip &= (xChip >= 0);
+        onChip &= (xChip < readoutMask->mask->numCols);
+        onChip &= (yChip >= 0);
+        onChip &= (yChip < readoutMask->mask->numRows);
+
+        if (onChip) {
+            psImageMaskType value = readoutMask->mask->data.PS_TYPE_IMAGE_MASK_DATA[yChip][xChip];
+            if (value & streakMaskValue) {
+                // skip sources on streak masks
+                continue;
+            }
+        }
+        psArrayAdd (outSources, 100, source);
+    }
+
+    psLogMsg ("censor", PS_LOG_INFO, "keeping %ld of %ld sources", outSources->n, inSources->n);
+
+    psFree (detections->allSources);
+    detections->allSources = outSources;
+
+    return true;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorVersion.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorVersion.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorVersion.c	(revision 41117)
@@ -0,0 +1,115 @@
+/** @file censorVersion.c
+ *
+ *  @brief
+ *
+ *  @ingroup censor
+ *
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "censor.h"
+#include "censorVersionDefinitions.h"
+
+#ifndef CENSOR_VERSION
+#error "CENSOR_VERSION is not set"
+#endif
+#ifndef CENSOR_BRANCH
+#error "CENSOR_BRANCH is not set"
+#endif
+#ifndef CENSOR_SOURCE
+#error "CENSOR_SOURCE is not set"
+#endif
+
+psString censorVersion(void)
+{
+    char *value = NULL;
+    psStringAppend(&value, "%s@%s", CENSOR_BRANCH, CENSOR_VERSION);
+    return value;
+}
+
+psString censorSource(void)
+{
+  return psStringCopy(CENSOR_SOURCE);
+}
+
+psString censorVersionLong(void)
+{
+    psString version = censorVersion();  // Version, to return
+    psString source = censorSource();    // Source
+
+    psStringPrepend(&version, "censor ");
+    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 censorVersionHeader(psMetadata *header)
+{
+    PS_ASSERT_METADATA_NON_NULL(header, false);
+
+    psString version = censorVersion(); // Software version
+    psString source = censorSource();   // Software source
+
+    psMetadataAddStr(header, PS_LIST_TAIL, "CENSOR_V", 0, NULL, source);
+    
+    psStringPrepend(&version, "censor version: ");
+    psStringPrepend(&source, "censor 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 censorVersionHeaderFull(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, "censor at %s", timeString);
+    psFree(timeString);
+    psMetadataAddStr(header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, history);
+    psFree(history);
+
+    psLibVersionHeader(header);
+    psModulesVersionHeader(header);
+    censorVersionHeader(header);
+
+    return true;
+}
+
+void censorVersionPrint(void)
+{
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psLogMsg("censor", PS_LOG_INFO, "censor at %s", timeString);
+    psFree(timeString);
+
+    psString pslib = psLibVersionLong();// psLib version
+    psString psmodules = psModulesVersionLong(); // psModules version
+    psString censor = censorVersionLong(); // censor version
+
+    psLogMsg("censor", PS_LOG_INFO, "%s", pslib);
+    psLogMsg("censor", PS_LOG_INFO, "%s", psmodules);
+    psLogMsg("censor", PS_LOG_INFO, "%s", censor);
+
+    psFree(pslib);
+    psFree(psmodules);
+    psFree(censor);
+
+    return;
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorVersionDefinitions.h.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorVersionDefinitions.h.in	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/censorObjects/src/censorVersionDefinitions.h.in	(revision 41117)
@@ -0,0 +1,8 @@
+#ifndef CENSOR_VERSION_DEFINITIONS_H
+#define CENSOR_VERSION_DEFINITIONS_H
+
+#define CENSOR_VERSION @CENSOR_VERSION@ // SVN version
+#define CENSOR_BRANCH  @CENSOR_BRANCH@  // SVN branch
+#define CENSOR_SOURCE  @CENSOR_SOURCE@  // SVN source
+
+#endif
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/Doxyfile.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/Doxyfile.in	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/Doxyfile.in	(revision 41117)
@@ -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           = streaksremove
+
+# The PROJECT_NUMBER tag can be used to enter a project or revision number. 
+# This could be handy for archiving the generated documentation or 
+# if some version control system is used.
+
+PROJECT_NUMBER         = ipp-2.7.dev
+
+# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) 
+# base path where the generated documentation will be put. 
+# If a relative path is entered, it will be relative to the location 
+# where doxygen was started. If left blank the current directory will be used.
+
+OUTPUT_DIRECTORY       = ./docs
+
+# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create 
+# 4096 sub-directories (in 2 levels) under the output directory of each output 
+# format and will distribute the generated files over these directories. 
+# Enabling this option can be useful when feeding doxygen a huge amount of 
+# source files, where putting all generated files in the same directory would 
+# otherwise cause performance problems for the file system.
+
+CREATE_SUBDIRS         = NO
+
+# The OUTPUT_LANGUAGE tag is used to specify the language in which all 
+# documentation generated by doxygen is written. Doxygen will use this 
+# information to generate all constant output in the proper language. 
+# The default language is English, other supported languages are: 
+# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, 
+# Croatian, Czech, Danish, Dutch, Finnish, French, German, Greek, Hungarian, 
+# Italian, Japanese, Japanese-en (Japanese with English messages), Korean, 
+# Korean-en, Lithuanian, Norwegian, Polish, Portuguese, Romanian, Russian, 
+# Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian.
+
+OUTPUT_LANGUAGE        = English
+
+# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will 
+# include brief member descriptions after the members that are listed in 
+# the file and class documentation (similar to JavaDoc). 
+# Set to NO to disable this.
+
+BRIEF_MEMBER_DESC      = YES
+
+# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend 
+# the brief description of a member or function before the detailed description. 
+# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the 
+# brief descriptions will be completely suppressed.
+
+REPEAT_BRIEF           = YES
+
+# This tag implements a quasi-intelligent brief description abbreviator 
+# that is used to form the text in various listings. Each string 
+# in this list, if found as the leading text of the brief description, will be 
+# stripped from the text and the result after processing the whole list, is 
+# used as the annotated text. Otherwise, the brief description is used as-is. 
+# If left blank, the following values are used ("$name" is automatically 
+# replaced with the name of the entity): "The $name class" "The $name widget" 
+# "The $name file" "is" "provides" "specifies" "contains" 
+# "represents" "a" "an" "the"
+
+ABBREVIATE_BRIEF       = 
+
+# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then 
+# Doxygen will generate a detailed section even if there is only a brief 
+# description.
+
+ALWAYS_DETAILED_SEC    = NO
+
+# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all 
+# inherited members of a class in the documentation of that class as if those 
+# members were ordinary class members. Constructors, destructors and assignment 
+# operators of the base classes will not be shown.
+
+INLINE_INHERITED_MEMB  = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full 
+# path before files name in the file list and in the header files. If set 
+# to NO the shortest path that makes the file name unique will be used.
+
+FULL_PATH_NAMES        = NO
+
+# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag 
+# can be used to strip a user-defined part of the path. Stripping is 
+# only done if one of the specified strings matches the left-hand part of 
+# the path. The tag can be used to show relative paths in the file list. 
+# If left blank the directory from which doxygen is run is used as the 
+# path to strip.
+
+STRIP_FROM_PATH        = 
+
+# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of 
+# the path mentioned in the documentation of a class, which tells 
+# the reader which header file to include in order to use a class. 
+# If left blank only the name of the header file containing the class 
+# definition is used. Otherwise one should specify the include paths that 
+# are normally passed to the compiler using the -I flag.
+
+STRIP_FROM_INC_PATH    = 
+
+# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter 
+# (but less readable) file names. This can be useful is your file systems 
+# doesn't support long names like on DOS, Mac, or CD-ROM.
+
+SHORT_NAMES            = NO
+
+# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen 
+# will interpret the first line (until the first dot) of a JavaDoc-style 
+# comment as the brief description. If set to NO, the JavaDoc 
+# comments will behave just like regular Qt-style comments 
+# (thus requiring an explicit @brief command for a brief description.)
+
+JAVADOC_AUTOBRIEF      = NO
+
+# If the QT_AUTOBRIEF tag is set to YES then Doxygen will 
+# interpret the first line (until the first dot) of a Qt-style 
+# comment as the brief description. If set to NO, the comments 
+# will behave just like regular Qt-style comments (thus requiring 
+# an explicit \brief command for a brief description.)
+
+QT_AUTOBRIEF           = NO
+
+# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen 
+# treat a multi-line C++ special comment block (i.e. a block of //! or /// 
+# comments) as a brief description. This used to be the default behaviour. 
+# The new default is to treat a multi-line C++ comment block as a detailed 
+# description. Set this tag to YES if you prefer the old behaviour instead.
+
+MULTILINE_CPP_IS_BRIEF = NO
+
+# If the DETAILS_AT_TOP tag is set to YES then Doxygen 
+# will output the detailed description near the top, like JavaDoc.
+# If set to NO, the detailed description appears after the member 
+# documentation.
+
+DETAILS_AT_TOP         = NO
+
+# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented 
+# member inherits the documentation from any documented member that it 
+# re-implements.
+
+INHERIT_DOCS           = YES
+
+# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce 
+# a new page for each member. If set to NO, the documentation of a member will 
+# be part of the file/class/namespace that contains it.
+
+SEPARATE_MEMBER_PAGES  = NO
+
+# The TAB_SIZE tag can be used to set the number of spaces in a tab. 
+# Doxygen uses this value to replace tabs by spaces in code fragments.
+
+TAB_SIZE               = 4
+
+# This tag can be used to specify a number of aliases that acts 
+# as commands in the documentation. An alias has the form "name=value". 
+# For example adding "sideeffect=\par Side Effects:\n" will allow you to 
+# put the command \sideeffect (or @sideeffect) in the documentation, which 
+# will result in a user-defined paragraph with heading "Side Effects:". 
+# You can put \n's in the value part of an alias to insert newlines.
+
+ALIASES                = 
+
+# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C 
+# sources only. Doxygen will then generate output that is more tailored for C. 
+# For instance, some of the names that are used will be different. The list 
+# of all members will be omitted, etc.
+
+OPTIMIZE_OUTPUT_FOR_C  = NO
+
+# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java 
+# sources only. Doxygen will then generate output that is more tailored for Java. 
+# For instance, namespaces will be presented as packages, qualified scopes 
+# will look different, etc.
+
+OPTIMIZE_OUTPUT_JAVA   = NO
+
+# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want to 
+# include (a tag file for) the STL sources as input, then you should 
+# set this tag to YES in order to let doxygen match functions declarations and 
+# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. 
+# func(std::string) {}). This also make the inheritance and collaboration 
+# diagrams that involve STL classes more complete and accurate.
+
+BUILTIN_STL_SUPPORT    = NO
+
+# If you use Microsoft's C++/CLI language, you should set this option to YES to
+# enable parsing support.
+
+CPP_CLI_SUPPORT        = NO
+
+# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. 
+# Doxygen will parse them like normal C++ but will assume all classes use public 
+# instead of private inheritance when no explicit protection keyword is present.
+
+SIP_SUPPORT            = NO
+
+# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC 
+# tag is set to YES, then doxygen will reuse the documentation of the first 
+# member in the group (if any) for the other members of the group. By default 
+# all members of a group must be documented explicitly.
+
+DISTRIBUTE_GROUP_DOC   = NO
+
+# Set the SUBGROUPING tag to YES (the default) to allow class member groups of 
+# the same type (for instance a group of public functions) to be put as a 
+# subgroup of that type (e.g. under the Public Functions section). Set it to 
+# NO to prevent subgrouping. Alternatively, this can be done per class using 
+# the \nosubgrouping command.
+
+SUBGROUPING            = YES
+
+# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct (or union) is 
+# documented as struct with the name of the typedef. So 
+# typedef struct TypeS {} TypeT, will appear in the documentation as a struct 
+# with name TypeT. When disabled the typedef will appear as a member of a file, 
+# namespace, or class. And the struct will be named TypeS. This can typically 
+# be useful for C code where the coding convention is that all structs are 
+# typedef'ed and only the typedef is referenced never the struct's name.
+
+TYPEDEF_HIDES_STRUCT   = NO
+
+#---------------------------------------------------------------------------
+# Build related configuration options
+#---------------------------------------------------------------------------
+
+# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in 
+# documentation are documented, even if no documentation was available. 
+# Private class members and static file members will be hidden unless 
+# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES
+
+EXTRACT_ALL            = NO
+
+# If the EXTRACT_PRIVATE tag is set to YES all private members of a class 
+# will be included in the documentation.
+
+EXTRACT_PRIVATE        = NO
+
+# If the EXTRACT_STATIC tag is set to YES all static members of a file 
+# will be included in the documentation.
+
+EXTRACT_STATIC         = NO
+
+# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) 
+# defined locally in source files will be included in the documentation. 
+# If set to NO only classes defined in header files are included.
+
+EXTRACT_LOCAL_CLASSES  = YES
+
+# This flag is only useful for Objective-C code. When set to YES local 
+# methods, which are defined in the implementation section but not in 
+# the interface are included in the documentation. 
+# If set to NO (the default) only methods in the interface are included.
+
+EXTRACT_LOCAL_METHODS  = NO
+
+# If this flag is set to YES, the members of anonymous namespaces will be extracted 
+# and appear in the documentation as a namespace called 'anonymous_namespace{file}', 
+# where file will be replaced with the base name of the file that contains the anonymous 
+# namespace. By default anonymous namespace are hidden.
+
+EXTRACT_ANON_NSPACES   = NO
+
+# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all 
+# undocumented members of documented classes, files or namespaces. 
+# If set to NO (the default) these members will be included in the 
+# various overviews, but no documentation section is generated. 
+# This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_MEMBERS     = NO
+
+# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all 
+# undocumented classes that are normally visible in the class hierarchy. 
+# If set to NO (the default) these classes will be included in the various 
+# overviews. This option has no effect if EXTRACT_ALL is enabled.
+
+HIDE_UNDOC_CLASSES     = NO
+
+# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all 
+# friend (class|struct|union) declarations. 
+# If set to NO (the default) these declarations will be included in the 
+# documentation.
+
+HIDE_FRIEND_COMPOUNDS  = NO
+
+# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any 
+# documentation blocks found inside the body of a function. 
+# If set to NO (the default) these blocks will be appended to the 
+# function's detailed documentation block.
+
+HIDE_IN_BODY_DOCS      = NO
+
+# The INTERNAL_DOCS tag determines if documentation 
+# that is typed after a \internal command is included. If the tag is set 
+# to NO (the default) then the documentation will be excluded. 
+# Set it to YES to include the internal documentation.
+
+INTERNAL_DOCS          = NO
+
+# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate 
+# file names in lower-case letters. If set to YES upper-case letters are also 
+# allowed. This is useful if you have classes or files whose names only differ 
+# in case and if your file system supports case sensitive file names. Windows 
+# and Mac users are advised to set this option to NO.
+
+CASE_SENSE_NAMES       = YES
+
+# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen 
+# will show members with their full class and namespace scopes in the 
+# documentation. If set to YES the scope will be hidden.
+
+HIDE_SCOPE_NAMES       = NO
+
+# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen 
+# will put a list of the files that are included by a file in the documentation 
+# of that file.
+
+SHOW_INCLUDE_FILES     = YES
+
+# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] 
+# is inserted in the documentation for inline members.
+
+INLINE_INFO            = YES
+
+# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen 
+# will sort the (detailed) documentation of file and class members 
+# alphabetically by member name. If set to NO the members will appear in 
+# declaration order.
+
+SORT_MEMBER_DOCS       = YES
+
+# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the 
+# brief documentation of file, namespace and class members alphabetically 
+# by member name. If set to NO (the default) the members will appear in 
+# declaration order.
+
+SORT_BRIEF_DOCS        = NO
+
+# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be 
+# sorted by fully-qualified names, including namespaces. If set to 
+# NO (the default), the class list will be sorted only by class name, 
+# not including the namespace part. 
+# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES.
+# Note: This option applies only to the class list, not to the 
+# alphabetical list.
+
+SORT_BY_SCOPE_NAME     = NO
+
+# The GENERATE_TODOLIST tag can be used to enable (YES) or 
+# disable (NO) the todo list. This list is created by putting \todo 
+# commands in the documentation.
+
+GENERATE_TODOLIST      = YES
+
+# The GENERATE_TESTLIST tag can be used to enable (YES) or 
+# disable (NO) the test list. This list is created by putting \test 
+# commands in the documentation.
+
+GENERATE_TESTLIST      = YES
+
+# The GENERATE_BUGLIST tag can be used to enable (YES) or 
+# disable (NO) the bug list. This list is created by putting \bug 
+# commands in the documentation.
+
+GENERATE_BUGLIST       = YES
+
+# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or 
+# disable (NO) the deprecated list. This list is created by putting 
+# \deprecated commands in the documentation.
+
+GENERATE_DEPRECATEDLIST= YES
+
+# The ENABLED_SECTIONS tag can be used to enable conditional 
+# documentation sections, marked by \if sectionname ... \endif.
+
+ENABLED_SECTIONS       = 
+
+# The MAX_INITIALIZER_LINES tag determines the maximum number of lines 
+# the initial value of a variable or define consists of for it to appear in 
+# the documentation. If the initializer consists of more lines than specified 
+# here it will be hidden. Use a value of 0 to hide initializers completely. 
+# The appearance of the initializer of individual variables and defines in the 
+# documentation can be controlled using \showinitializer or \hideinitializer 
+# command in the documentation regardless of this setting.
+
+MAX_INITIALIZER_LINES  = 30
+
+# Set the SHOW_USED_FILES tag to NO to disable the list of files generated 
+# at the bottom of the documentation of classes and structs. If set to YES the 
+# list will mention the files that were used to generate the documentation.
+
+SHOW_USED_FILES        = YES
+
+# If the sources in your project are distributed over multiple directories 
+# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy 
+# in the documentation. The default is NO.
+
+SHOW_DIRECTORIES       = YES
+
+# The FILE_VERSION_FILTER tag can be used to specify a program or script that 
+# doxygen should invoke to get the current version for each file (typically from the 
+# version control system). Doxygen will invoke the program by executing (via 
+# popen()) the command <command> <input-file>, where <command> is the value of 
+# the FILE_VERSION_FILTER tag, and <input-file> is the name of an input file 
+# provided by doxygen. Whatever the program writes to standard output 
+# is used as the file version. See the manual for examples.
+
+FILE_VERSION_FILTER    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to warning and progress messages
+#---------------------------------------------------------------------------
+
+# The QUIET tag can be used to turn on/off the messages that are generated 
+# by doxygen. Possible values are YES and NO. If left blank NO is used.
+
+QUIET                  = NO
+
+# The WARNINGS tag can be used to turn on/off the warning messages that are 
+# generated by doxygen. Possible values are YES and NO. If left blank 
+# NO is used.
+
+WARNINGS               = YES
+
+# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings 
+# for undocumented members. If EXTRACT_ALL is set to YES then this flag will 
+# automatically be disabled.
+
+WARN_IF_UNDOCUMENTED   = YES
+
+# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for 
+# potential errors in the documentation, such as not documenting some 
+# parameters in a documented function, or documenting parameters that 
+# don't exist or using markup commands wrongly.
+
+WARN_IF_DOC_ERROR      = YES
+
+# This WARN_NO_PARAMDOC option can be abled to get warnings for 
+# functions that are documented, but have no documentation for their parameters 
+# or return value. If set to NO (the default) doxygen will only warn about 
+# wrong or incomplete parameter documentation, but not about the absence of 
+# documentation.
+
+WARN_NO_PARAMDOC       = NO
+
+# The WARN_FORMAT tag determines the format of the warning messages that 
+# doxygen can produce. The string should contain the $file, $line, and $text 
+# tags, which will be replaced by the file and line number from which the 
+# warning originated and the warning text. Optionally the format may contain 
+# $version, which will be replaced by the version of the file (if it could 
+# be obtained via FILE_VERSION_FILTER)
+
+WARN_FORMAT            = "$file:$line: $text "
+
+# The WARN_LOGFILE tag can be used to specify a file to which warning 
+# and error messages should be written. If left blank the output is written 
+# to stderr.
+
+WARN_LOGFILE           = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the input files
+#---------------------------------------------------------------------------
+
+# The INPUT tag can be used to specify the files and/or directories that contain 
+# documented source files. You may enter file names like "myfile.cpp" or 
+# directories like "/usr/src/myproject". Separate the files or directories 
+# with spaces.
+
+INPUT                  = ./src
+
+# This tag can be used to specify the character encoding of the source files that 
+# doxygen parses. Internally doxygen uses the UTF-8 encoding, which is also the default 
+# input encoding. Doxygen uses libiconv (or the iconv built into libc) for the transcoding. 
+# See http://www.gnu.org/software/libiconv for the list of possible encodings.
+
+INPUT_ENCODING         = UTF-8
+
+# If the value of the INPUT tag contains directories, you can use the 
+# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank the following patterns are tested: 
+# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx 
+# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90
+
+FILE_PATTERNS          = *.h
+
+# The RECURSIVE tag can be used to turn specify whether or not subdirectories 
+# should be searched for input files as well. Possible values are YES and NO. 
+# If left blank NO is used.
+
+RECURSIVE              = YES
+
+# The EXCLUDE tag can be used to specify files and/or directories that should 
+# excluded from the INPUT source files. This way you can easily exclude a 
+# subdirectory from a directory tree whose root is specified with the INPUT tag.
+
+EXCLUDE                = 
+
+# The EXCLUDE_SYMLINKS tag can be used select whether or not files or 
+# directories that are symbolic links (a Unix filesystem feature) are excluded 
+# from the input.
+
+EXCLUDE_SYMLINKS       = NO
+
+# If the value of the INPUT tag contains directories, you can use the 
+# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude 
+# certain files from those directories. Note that the wildcards are matched 
+# against the file with absolute path, so to exclude all test directories 
+# for example use the pattern */test/*
+
+EXCLUDE_PATTERNS       = 
+
+# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names 
+# (namespaces, classes, functions, etc.) that should be excluded from the output. 
+# The symbol name can be a fully qualified name, a word, or if the wildcard * is used, 
+# a substring. Examples: ANamespace, AClass, AClass::ANamespace, ANamespace::*Test
+
+EXCLUDE_SYMBOLS        = 
+
+# The EXAMPLE_PATH tag can be used to specify one or more files or 
+# directories that contain example code fragments that are included (see 
+# the \include command).
+
+EXAMPLE_PATH           = 
+
+# If the value of the EXAMPLE_PATH tag contains directories, you can use the 
+# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp 
+# and *.h) to filter out the source-files in the directories. If left 
+# blank all files are included.
+
+EXAMPLE_PATTERNS       = 
+
+# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be 
+# searched for input files to be used with the \include or \dontinclude 
+# commands irrespective of the value of the RECURSIVE tag. 
+# Possible values are YES and NO. If left blank NO is used.
+
+EXAMPLE_RECURSIVE      = NO
+
+# The IMAGE_PATH tag can be used to specify one or more files or 
+# directories that contain image that are included in the documentation (see 
+# the \image command).
+
+IMAGE_PATH             = 
+
+# The INPUT_FILTER tag can be used to specify a program that doxygen should 
+# invoke to filter for each input file. Doxygen will invoke the filter program 
+# by executing (via popen()) the command <filter> <input-file>, where <filter> 
+# is the value of the INPUT_FILTER tag, and <input-file> is the name of an 
+# input file. Doxygen will then use the output that the filter program writes 
+# to standard output.  If FILTER_PATTERNS is specified, this tag will be 
+# ignored.
+
+INPUT_FILTER           = 
+
+# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern 
+# basis.  Doxygen will compare the file name with each pattern and apply the 
+# filter if there is a match.  The filters are a list of the form: 
+# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further 
+# info on how filters are used. If FILTER_PATTERNS is empty, INPUT_FILTER 
+# is applied to all files.
+
+FILTER_PATTERNS        = 
+
+# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using 
+# INPUT_FILTER) will be used to filter the input files when producing source 
+# files to browse (i.e. when SOURCE_BROWSER is set to YES).
+
+FILTER_SOURCE_FILES    = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to source browsing
+#---------------------------------------------------------------------------
+
+# If the SOURCE_BROWSER tag is set to YES then a list of source files will 
+# be generated. Documented entities will be cross-referenced with these sources. 
+# Note: To get rid of all source code in the generated output, make sure also 
+# VERBATIM_HEADERS is set to NO. If you have enabled CALL_GRAPH or CALLER_GRAPH 
+# then you must also enable this option. If you don't then doxygen will produce 
+# a warning and turn it on anyway
+
+SOURCE_BROWSER         = NO
+
+# Setting the INLINE_SOURCES tag to YES will include the body 
+# of functions and classes directly in the documentation.
+
+INLINE_SOURCES         = NO
+
+# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct 
+# doxygen to hide any special comment blocks from generated source code 
+# fragments. Normal C and C++ comments will always remain visible.
+
+STRIP_CODE_COMMENTS    = YES
+
+# If the REFERENCED_BY_RELATION tag is set to YES (the default) 
+# then for each documented function all documented 
+# functions referencing it will be listed.
+
+REFERENCED_BY_RELATION = YES
+
+# If the REFERENCES_RELATION tag is set to YES (the default) 
+# then for each documented function all documented entities 
+# called/used by that function will be listed.
+
+REFERENCES_RELATION    = YES
+
+# If the REFERENCES_LINK_SOURCE tag is set to YES (the default)
+# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from
+# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will
+# link to the source code.  Otherwise they will link to the documentstion.
+
+REFERENCES_LINK_SOURCE = YES
+
+# If the USE_HTAGS tag is set to YES then the references to source code 
+# will point to the HTML generated by the htags(1) tool instead of doxygen 
+# built-in source browser. The htags tool is part of GNU's global source 
+# tagging system (see http://www.gnu.org/software/global/global.html). You 
+# will need version 4.8.6 or higher.
+
+USE_HTAGS              = NO
+
+# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen 
+# will generate a verbatim copy of the header file for each class for 
+# which an include is specified. Set to NO to disable this.
+
+VERBATIM_HEADERS       = YES
+
+#---------------------------------------------------------------------------
+# configuration options related to the alphabetical class index
+#---------------------------------------------------------------------------
+
+# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index 
+# of all compounds will be generated. Enable this if the project 
+# contains a lot of classes, structs, unions or interfaces.
+
+ALPHABETICAL_INDEX     = NO
+
+# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then 
+# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns 
+# in which this list will be split (can be a number in the range [1..20])
+
+COLS_IN_ALPHA_INDEX    = 5
+
+# In case all classes in a project start with a common prefix, all 
+# classes will be put under the same header in the alphabetical index. 
+# The IGNORE_PREFIX tag can be used to specify one or more prefixes that 
+# should be ignored while generating the index headers.
+
+IGNORE_PREFIX          = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the HTML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_HTML tag is set to YES (the default) Doxygen will 
+# generate HTML output.
+
+GENERATE_HTML          = YES
+
+# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `html' will be used as the default path.
+
+HTML_OUTPUT            = html
+
+# The HTML_FILE_EXTENSION tag can be used to specify the file extension for 
+# each generated HTML page (for example: .htm,.php,.asp). If it is left blank 
+# doxygen will generate files with .html extension.
+
+HTML_FILE_EXTENSION    = .html
+
+# The HTML_HEADER tag can be used to specify a personal HTML header for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard header.
+
+HTML_HEADER            = 
+
+# The HTML_FOOTER tag can be used to specify a personal HTML footer for 
+# each generated HTML page. If it is left blank doxygen will generate a 
+# standard footer.
+
+HTML_FOOTER            = 
+
+# The HTML_STYLESHEET tag can be used to specify a user-defined cascading 
+# style sheet that is used by each HTML page. It can be used to 
+# fine-tune the look of the HTML output. If the tag is left blank doxygen 
+# will generate a default style sheet. Note that doxygen will try to copy 
+# the style sheet file to the HTML output directory, so don't put your own 
+# stylesheet in the HTML output directory as well, or it will be erased!
+
+HTML_STYLESHEET        = 
+
+# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, 
+# files or namespaces will be aligned in HTML using tables. If set to 
+# NO a bullet list will be used.
+
+HTML_ALIGN_MEMBERS     = YES
+
+# If the GENERATE_HTMLHELP tag is set to YES, additional index files 
+# will be generated that can be used as input for tools like the 
+# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) 
+# of the generated HTML documentation.
+
+GENERATE_HTMLHELP      = NO
+
+# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML 
+# documentation will contain sections that can be hidden and shown after the 
+# page has loaded. For this to work a browser that supports 
+# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox 
+# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari).
+
+HTML_DYNAMIC_SECTIONS  = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can 
+# be used to specify the file name of the resulting .chm file. You 
+# can add a path in front of the file if the result should not be 
+# written to the html output directory.
+
+CHM_FILE               = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can 
+# be used to specify the location (absolute path including file name) of 
+# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run 
+# the HTML help compiler on the generated index.hhp.
+
+HHC_LOCATION           = 
+
+# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag 
+# controls if a separate .chi index file is generated (YES) or that 
+# it should be included in the master .chm file (NO).
+
+GENERATE_CHI           = NO
+
+# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag 
+# controls whether a binary table of contents is generated (YES) or a 
+# normal table of contents (NO) in the .chm file.
+
+BINARY_TOC             = NO
+
+# The TOC_EXPAND flag can be set to YES to add extra items for group members 
+# to the contents of the HTML help documentation and to the tree view.
+
+TOC_EXPAND             = NO
+
+# The DISABLE_INDEX tag can be used to turn on/off the condensed index at 
+# top of each HTML page. The value NO (the default) enables the index and 
+# the value YES disables it.
+
+DISABLE_INDEX          = NO
+
+# This tag can be used to set the number of enum values (range [1..20]) 
+# that doxygen will group on one line in the generated HTML documentation.
+
+ENUM_VALUES_PER_LINE   = 4
+
+# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be
+# generated containing a tree-like index structure (just like the one that 
+# is generated for HTML Help). For this to work a browser that supports 
+# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, 
+# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are 
+# probably better off using the HTML help feature.
+
+GENERATE_TREEVIEW      = NO
+
+# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be 
+# used to set the initial width (in pixels) of the frame in which the tree 
+# is shown.
+
+TREEVIEW_WIDTH         = 250
+
+#---------------------------------------------------------------------------
+# configuration options related to the LaTeX output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will 
+# generate Latex output.
+
+GENERATE_LATEX         = NO
+
+# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `latex' will be used as the default path.
+
+LATEX_OUTPUT           = latex
+
+# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be 
+# invoked. If left blank `latex' will be used as the default command name.
+
+LATEX_CMD_NAME         = latex
+
+# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to 
+# generate index for LaTeX. If left blank `makeindex' will be used as the 
+# default command name.
+
+MAKEINDEX_CMD_NAME     = makeindex
+
+# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact 
+# LaTeX documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_LATEX          = NO
+
+# The PAPER_TYPE tag can be used to set the paper type that is used 
+# by the printer. Possible values are: a4, a4wide, letter, legal and 
+# executive. If left blank a4wide will be used.
+
+PAPER_TYPE             = a4wide
+
+# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX 
+# packages that should be included in the LaTeX output.
+
+EXTRA_PACKAGES         = 
+
+# The LATEX_HEADER tag can be used to specify a personal LaTeX header for 
+# the generated latex document. The header should contain everything until 
+# the first chapter. If it is left blank doxygen will generate a 
+# standard header. Notice: only use this tag if you know what you are doing!
+
+LATEX_HEADER           = 
+
+# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated 
+# is prepared for conversion to pdf (using ps2pdf). The pdf file will 
+# contain links (just like the HTML output) instead of page references 
+# This makes the output suitable for online browsing using a pdf viewer.
+
+PDF_HYPERLINKS         = NO
+
+# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of 
+# plain latex in the generated Makefile. Set this option to YES to get a 
+# higher quality PDF documentation.
+
+USE_PDFLATEX           = NO
+
+# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. 
+# command to the generated LaTeX files. This will instruct LaTeX to keep 
+# running if errors occur, instead of asking the user for help. 
+# This option is also used when generating formulas in HTML.
+
+LATEX_BATCHMODE        = NO
+
+# If LATEX_HIDE_INDICES is set to YES then doxygen will not 
+# include the index chapters (such as File Index, Compound Index, etc.) 
+# in the output.
+
+LATEX_HIDE_INDICES     = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the RTF output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output 
+# The RTF output is optimized for Word 97 and may not look very pretty with 
+# other RTF readers or editors.
+
+GENERATE_RTF           = NO
+
+# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `rtf' will be used as the default path.
+
+RTF_OUTPUT             = rtf
+
+# If the COMPACT_RTF tag is set to YES Doxygen generates more compact 
+# RTF documents. This may be useful for small projects and may help to 
+# save some trees in general.
+
+COMPACT_RTF            = NO
+
+# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated 
+# will contain hyperlink fields. The RTF file will 
+# contain links (just like the HTML output) instead of page references. 
+# This makes the output suitable for online browsing using WORD or other 
+# programs which support those fields. 
+# Note: wordpad (write) and others do not support links.
+
+RTF_HYPERLINKS         = NO
+
+# Load stylesheet definitions from file. Syntax is similar to doxygen's 
+# config file, i.e. a series of assignments. You only have to provide 
+# replacements, missing definitions are set to their default value.
+
+RTF_STYLESHEET_FILE    = 
+
+# Set optional variables used in the generation of an rtf document. 
+# Syntax is similar to doxygen's config file.
+
+RTF_EXTENSIONS_FILE    = 
+
+#---------------------------------------------------------------------------
+# configuration options related to the man page output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_MAN tag is set to YES (the default) Doxygen will 
+# generate man pages
+
+GENERATE_MAN           = YES
+
+# The MAN_OUTPUT tag is used to specify where the man pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `man' will be used as the default path.
+
+MAN_OUTPUT             = man
+
+# The MAN_EXTENSION tag determines the extension that is added to 
+# the generated man pages (default is the subroutine's section .3)
+
+MAN_EXTENSION          = .3
+
+# If the MAN_LINKS tag is set to YES and Doxygen generates man output, 
+# then it will generate one additional man file for each entity 
+# documented in the real man page(s). These additional files 
+# only source the real man page, but without them the man command 
+# would be unable to find the correct page. The default is NO.
+
+MAN_LINKS              = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the XML output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_XML tag is set to YES Doxygen will 
+# generate an XML file that captures the structure of 
+# the code including all documentation.
+
+GENERATE_XML           = NO
+
+# The XML_OUTPUT tag is used to specify where the XML pages will be put. 
+# If a relative path is entered the value of OUTPUT_DIRECTORY will be 
+# put in front of it. If left blank `xml' will be used as the default path.
+
+XML_OUTPUT             = xml
+
+# The XML_SCHEMA tag can be used to specify an XML schema, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_SCHEMA             = 
+
+# The XML_DTD tag can be used to specify an XML DTD, 
+# which can be used by a validating XML parser to check the 
+# syntax of the XML files.
+
+XML_DTD                = 
+
+# If the XML_PROGRAMLISTING tag is set to YES Doxygen will 
+# dump the program listings (including syntax highlighting 
+# and cross-referencing information) to the XML output. Note that 
+# enabling this will significantly increase the size of the XML output.
+
+XML_PROGRAMLISTING     = YES
+
+#---------------------------------------------------------------------------
+# configuration options for the AutoGen Definitions output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will 
+# generate an AutoGen Definitions (see autogen.sf.net) file 
+# that captures the structure of the code including all 
+# documentation. Note that this feature is still experimental 
+# and incomplete at the moment.
+
+GENERATE_AUTOGEN_DEF   = NO
+
+#---------------------------------------------------------------------------
+# configuration options related to the Perl module output
+#---------------------------------------------------------------------------
+
+# If the GENERATE_PERLMOD tag is set to YES Doxygen will 
+# generate a Perl module file that captures the structure of 
+# the code including all documentation. Note that this 
+# feature is still experimental and incomplete at the 
+# moment.
+
+GENERATE_PERLMOD       = NO
+
+# If the PERLMOD_LATEX tag is set to YES Doxygen will generate 
+# the necessary Makefile rules, Perl scripts and LaTeX code to be able 
+# to generate PDF and DVI output from the Perl module output.
+
+PERLMOD_LATEX          = NO
+
+# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be 
+# nicely formatted so it can be parsed by a human reader.  This is useful 
+# if you want to understand what is going on.  On the other hand, if this 
+# tag is set to NO the size of the Perl module output will be much smaller 
+# and Perl will parse it just the same.
+
+PERLMOD_PRETTY         = YES
+
+# The names of the make variables in the generated doxyrules.make file 
+# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. 
+# This is useful so different doxyrules.make files included by the same 
+# Makefile don't overwrite each other's variables.
+
+PERLMOD_MAKEVAR_PREFIX = 
+
+#---------------------------------------------------------------------------
+# Configuration options related to the preprocessor   
+#---------------------------------------------------------------------------
+
+# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will 
+# evaluate all C-preprocessor directives found in the sources and include 
+# files.
+
+ENABLE_PREPROCESSING   = YES
+
+# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro 
+# names in the source code. If set to NO (the default) only conditional 
+# compilation will be performed. Macro expansion can be done in a controlled 
+# way by setting EXPAND_ONLY_PREDEF to YES.
+
+MACRO_EXPANSION        = NO
+
+# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES 
+# then the macro expansion is limited to the macros specified with the 
+# PREDEFINED and EXPAND_AS_DEFINED tags.
+
+EXPAND_ONLY_PREDEF     = NO
+
+# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files 
+# in the INCLUDE_PATH (see below) will be search if a #include is found.
+
+SEARCH_INCLUDES        = YES
+
+# The INCLUDE_PATH tag can be used to specify one or more directories that 
+# contain include files that are not input files but should be processed by 
+# the preprocessor.
+
+INCLUDE_PATH           = 
+
+# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard 
+# patterns (like *.h and *.hpp) to filter out the header-files in the 
+# directories. If left blank, the patterns specified with FILE_PATTERNS will 
+# be used.
+
+INCLUDE_FILE_PATTERNS  = 
+
+# The PREDEFINED tag can be used to specify one or more macro names that 
+# are defined before the preprocessor is started (similar to the -D option of 
+# gcc). The argument of the tag is a list of macros of the form: name 
+# or name=definition (no spaces). If the definition and the = are 
+# omitted =1 is assumed. To prevent a macro definition from being 
+# undefined via #undef or recursively expanded use the := operator 
+# instead of the = operator.
+
+PREDEFINED             = 
+
+# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then 
+# this tag can be used to specify a list of macro names that should be expanded. 
+# The macro definition that is found in the sources will be used. 
+# Use the PREDEFINED tag if you want to use a different macro definition.
+
+EXPAND_AS_DEFINED      = 
+
+# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then 
+# doxygen's preprocessor will remove all function-like macros that are alone 
+# on a line, have an all uppercase name, and do not end with a semicolon. Such 
+# function macros are typically used for boiler-plate code, and will confuse 
+# the parser if not removed.
+
+SKIP_FUNCTION_MACROS   = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to external references   
+#---------------------------------------------------------------------------
+
+# The TAGFILES option can be used to specify one or more tagfiles. 
+# Optionally an initial location of the external documentation 
+# can be added for each tagfile. The format of a tag file without 
+# this location is as follows: 
+#   TAGFILES = file1 file2 ... 
+# Adding location for the tag files is done as follows: 
+#   TAGFILES = file1=loc1 "file2 = loc2" ... 
+# where "loc1" and "loc2" can be relative or absolute paths or 
+# URLs. If a location is present for each tag, the installdox tool 
+# does not have to be run to correct the links.
+# Note that each tag file must have a unique name
+# (where the name does NOT include the path)
+# If a tag file is not located in the directory in which doxygen 
+# is run, you must also specify the path to the tagfile here.
+
+TAGFILES               = 
+
+# When a file name is specified after GENERATE_TAGFILE, doxygen will create 
+# a tag file that is based on the input files it reads.
+
+GENERATE_TAGFILE       = 
+
+# If the ALLEXTERNALS tag is set to YES all external classes will be listed 
+# in the class index. If set to NO only the inherited external classes 
+# will be listed.
+
+ALLEXTERNALS           = NO
+
+# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed 
+# in the modules index. If set to NO, only the current project's groups will 
+# be listed.
+
+EXTERNAL_GROUPS        = YES
+
+# The PERL_PATH should be the absolute path and name of the perl script 
+# interpreter (i.e. the result of `which perl').
+
+PERL_PATH              = /usr/bin/perl
+
+#---------------------------------------------------------------------------
+# Configuration options related to the dot tool   
+#---------------------------------------------------------------------------
+
+# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will 
+# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base 
+# or super classes. Setting the tag to NO turns the diagrams off. Note that 
+# this option is superseded by the HAVE_DOT option below. This is only a 
+# fallback. It is recommended to install and use dot, since it yields more 
+# powerful graphs.
+
+CLASS_DIAGRAMS         = YES
+
+# You can define message sequence charts within doxygen comments using the \msc 
+# command. Doxygen will then run the mscgen tool (see http://www.mcternan.me.uk/mscgen/) to 
+# produce the chart and insert it in the documentation. The MSCGEN_PATH tag allows you to 
+# specify the directory where the mscgen tool resides. If left empty the tool is assumed to 
+# be found in the default search path.
+
+MSCGEN_PATH            = 
+
+# If set to YES, the inheritance and collaboration graphs will hide 
+# inheritance and usage relations if the target is undocumented 
+# or is not a class.
+
+HIDE_UNDOC_RELATIONS   = YES
+
+# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is 
+# available from the path. This tool is part of Graphviz, a graph visualization 
+# toolkit from AT&T and Lucent Bell Labs. The other options in this section 
+# have no effect if this option is set to NO (the default)
+
+HAVE_DOT               = NO
+
+# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect inheritance relations. Setting this tag to YES will force the 
+# the CLASS_DIAGRAMS tag to NO.
+
+CLASS_GRAPH            = YES
+
+# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for each documented class showing the direct and 
+# indirect implementation dependencies (inheritance, containment, and 
+# class references variables) of the class with other documented classes.
+
+COLLABORATION_GRAPH    = YES
+
+# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen 
+# will generate a graph for groups, showing the direct groups dependencies
+
+GROUP_GRAPHS           = YES
+
+# If the UML_LOOK tag is set to YES doxygen will generate inheritance and 
+# collaboration diagrams in a style similar to the OMG's Unified Modeling 
+# Language.
+
+UML_LOOK               = NO
+
+# If set to YES, the inheritance and collaboration graphs will show the 
+# relations between templates and their instances.
+
+TEMPLATE_RELATIONS     = NO
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT 
+# tags are set to YES then doxygen will generate a graph for each documented 
+# file showing the direct and indirect include dependencies of the file with 
+# other documented files.
+
+INCLUDE_GRAPH          = YES
+
+# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and 
+# HAVE_DOT tags are set to YES then doxygen will generate a graph for each 
+# documented header file showing the documented files that directly or 
+# indirectly include this file.
+
+INCLUDED_BY_GRAPH      = YES
+
+# If the CALL_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will 
+# generate a call dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable call graphs for selected 
+# functions only using the \callgraph command.
+
+CALL_GRAPH             = NO
+
+# If the CALLER_GRAPH, SOURCE_BROWSER and HAVE_DOT tags are set to YES then doxygen will 
+# generate a caller dependency graph for every global function or class method. 
+# Note that enabling this option will significantly increase the time of a run. 
+# So in most cases it will be better to enable caller graphs for selected 
+# functions only using the \callergraph command.
+
+CALLER_GRAPH           = NO
+
+# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen 
+# will graphical hierarchy of all classes instead of a textual one.
+
+GRAPHICAL_HIERARCHY    = YES
+
+# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES and HAVE_DOT tags are set to YES 
+# then doxygen will show the dependencies a directory has on other directories 
+# in a graphical way. The dependency relations are determined by the #include
+# relations between the files in the directories.
+
+DIRECTORY_GRAPH        = YES
+
+# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images 
+# generated by dot. Possible values are png, jpg, or gif
+# If left blank png will be used.
+
+DOT_IMAGE_FORMAT       = png
+
+# The tag DOT_PATH can be used to specify the path where the dot tool can be 
+# found. If left blank, it is assumed the dot tool can be found in the path.
+
+DOT_PATH               = 
+
+# The DOTFILE_DIRS tag can be used to specify one or more directories that 
+# contain dot files that are included in the documentation (see the 
+# \dotfile command).
+
+DOTFILE_DIRS           = 
+
+# The MAX_DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of 
+# nodes that will be shown in the graph. If the number of nodes in a graph 
+# becomes larger than this value, doxygen will truncate the graph, which is 
+# visualized by representing a node as a red box. Note that doxygen if the number 
+# of direct children of the root node in a graph is already larger than 
+# MAX_DOT_GRAPH_NOTES then the graph will not be shown at all. Also note 
+# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH.
+
+DOT_GRAPH_MAX_NODES    = 50
+
+# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the 
+# graphs generated by dot. A depth value of 3 means that only nodes reachable 
+# from the root by following a path via at most 3 edges will be shown. Nodes 
+# that lay further from the root node will be omitted. Note that setting this 
+# option to 1 or 2 may greatly reduce the computation time needed for large 
+# code bases. Also note that the size of a graph can be further restricted by 
+# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction.
+
+MAX_DOT_GRAPH_DEPTH    = 0
+
+# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent 
+# background. This is disabled by default, which results in a white background. 
+# Warning: Depending on the platform used, enabling this option may lead to 
+# badly anti-aliased labels on the edges of a graph (i.e. they become hard to 
+# read).
+
+DOT_TRANSPARENT        = NO
+
+# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output 
+# files in one run (i.e. multiple -o and -T options on the command line). This 
+# makes dot run faster, but since only newer versions of dot (>1.8.10) 
+# support this, this feature is disabled by default.
+
+DOT_MULTI_TARGETS      = NO
+
+# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will 
+# generate a legend page explaining the meaning of the various boxes and 
+# arrows in the dot generated graphs.
+
+GENERATE_LEGEND        = YES
+
+# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will 
+# remove the intermediate dot files that are used to generate 
+# the various graphs.
+
+DOT_CLEANUP            = YES
+
+#---------------------------------------------------------------------------
+# Configuration::additions related to the search engine   
+#---------------------------------------------------------------------------
+
+# The SEARCHENGINE tag specifies whether or not a search engine should be 
+# used. If set to NO the values of all tags below this one will be ignored.
+
+SEARCHENGINE           = NO
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/Makefile.am
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/Makefile.am	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/Makefile.am	(revision 41117)
@@ -0,0 +1,4 @@
+SUBDIRS = src
+
+CLEANFILES = *.pyc *~ core core.*
+
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/autogen.sh
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/autogen.sh	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/autogen.sh	(revision 41117)
@@ -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=streaks
+TEST_TYPE=-f
+# change this to be a unique filename in the top level dir
+FILE=autogen.sh
+
+DIE=0
+
+LIBTOOLIZE=libtoolize
+ACLOCAL="aclocal $ACLOCAL_FLAGS"
+AUTOHEADER=autoheader
+AUTOMAKE=automake
+AUTOCONF=autoconf
+
+($LIBTOOLIZE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $LIBTOOLIZE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/libtool/"
+        DIE=1
+}
+
+($ACLOCAL --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $ACLOCAL installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOHEADER --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOHEADER installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+($AUTOMAKE --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOMAKE installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/automake/"
+        DIE=1
+}
+
+($AUTOCONF --version) < /dev/null > /dev/null 2>&1 || {
+        echo
+        echo "You must have $AUTOCONF installed to compile $PROJECT."
+        echo "Download the appropriate package for your distribution,"
+        echo "or get the source tarball at http://ftp.gnu.org/gnu/autoconf/"
+        DIE=1
+}
+
+if test "$DIE" -eq 1; then
+        exit 1
+fi
+
+test $TEST_TYPE $FILE || {
+        echo "You must run this script in the top-level $PROJECT directory"
+        exit 1
+}
+
+if test -z "$*"; then
+        echo "I am going to run ./configure with no arguments - if you wish "
+        echo "to pass any to it, please specify them on the $0 command line."
+fi
+
+$LIBTOOLIZE --copy --force || echo "$LIBTOOLIZE failed"
+$ACLOCAL || echo "$ACLOCAL failed"
+$AUTOHEADER || echo "$AUTOHEADER failed"
+$AUTOMAKE --add-missing --force-missing --copy || echo "$AUTOMAKE  failed"
+$AUTOCONF || echo "$AUTOCONF failed"
+
+cd $ORIGDIR
+
+run_configure=true
+for arg in $*; do
+    case $arg in
+        --no-configure)
+            run_configure=false
+            ;;
+        *)
+            ;;
+    esac
+done
+
+if $run_configure; then
+    $srcdir/configure --enable-maintainer-mode "$@"
+    echo
+    echo "Now type 'make' to compile $PROJECT."
+else
+    echo
+    echo "Now run 'configure' and 'make' to compile $PROJECT."
+fi
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/configure.ac
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/configure.ac	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/configure.ac	(revision 41117)
@@ -0,0 +1,45 @@
+dnl Process this file with autoconf to produce a configure script.
+AC_PREREQ(2.61)
+
+AC_INIT([streaksremove], [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_STDLDFLAGS
+
+AC_LANG(C)
+AC_GNU_SOURCE
+AC_PROG_CC_C99
+AC_PROG_INSTALL
+AC_PROG_LIBTOOL
+AC_SYS_LARGEFILE
+
+dnl ------------------------------------------------------------
+
+dnl ------------- psLib, psModules ---------------
+PKG_CHECK_MODULES([PSLIB],     [pslib >= 1.0.0])
+PKG_CHECK_MODULES([PSMODULE],  [psmodules >= 1.0.0])
+PKG_CHECK_MODULES([NEBCLIENT], [nebclient >= 0.17])
+
+dnl Set CFLAGS for build
+IPP_STDOPTS
+IPP_STDCFLAGS
+
+echo "STREAKSREMOVE_CFLAGS: $STREAKSREMOVE_CFLAGS"
+echo "STREAKSREMOVE_LIBS: $STREAKSREMOVE_LIBS"
+
+IPP_VERSION
+
+AC_SUBST([STREAKSREMOVE_CFLAGS])
+AC_SUBST([STREAKSREMOVE_LIBS])
+
+AC_CONFIG_FILES([
+  Makefile
+  src/Makefile
+  Doxyfile
+])
+
+AC_OUTPUT
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/Line.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/Line.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/Line.c	(revision 41117)
@@ -0,0 +1,507 @@
+/* Standard includes */
+
+#include <math.h>
+
+/* psLib includes */
+
+#include "psMemory.h"
+
+/* streakremove includes */
+
+#include "Line.h"
+
+/** Internal routine to swap double values */
+
+void SwapDouble (double* first, double* second)
+{
+    double temp = *first;
+    *first = *second;
+    *second = temp;
+}
+
+/** Internal routine to swap integer values */
+
+void SwapInt (int* first, int* second)
+{
+    int temp = *first;
+    *first = *second;
+    *second = temp;
+}
+
+/** Return if a point is inside the current line segment assuming the
+    point and line are colinear                                         */
+
+bool InLineSegment (Line* line, strkPt* tuple)
+{
+    // If the current line is not vertical, check to see if the point
+    // is inside the two endpoints independent of direction
+
+    if (line->begin.x != line->end.x)
+    {
+        return (tuple->x >= line->begin.x &&
+                tuple->x <= line->end.x) ||
+               (tuple->x >= line->end.x &&
+                tuple->x <= line->begin.x);
+    }
+    return (tuple->y >= line->begin.y &&
+            tuple->y <= line->end.y) ||
+           (tuple->y >= line->end.y &&
+            tuple->y <= line->begin.y);
+}
+
+double DistanceSquared (Line* line, double x, double y)
+{
+    // Define U as the vector from the line segment start to end
+    // Define V as the vector from the line segment start to the tuple
+    // Define W as the vector from the line segment end to the tuple
+
+    double ux, uy, vx, vy, b, px, py;
+    ux = line->end.x - line->begin.x;
+    uy = line->end.y - line->begin.y;
+
+    vx = x - line->begin.x;
+    vy = y - line->begin.y;
+
+    double u_u = ux * ux + uy * uy;                         // (u . u)
+    double u_v = ux * vx + uy * vy;                         // (u . v)
+
+    if (u_v <= 0) return vx * vx + vy * vy;                 // (v . v)
+    if (u_u <= u_v)
+    {
+        double wx =  x - line->end.x;
+        double wy =  y - line->end.y;
+        return wx * wx + wy * wy;                           // (w . w)
+    }
+
+    // Compute P(b) is the base of the perpendicular dropped from tuple to
+    // the line
+
+    b = u_v / u_u;
+    px = vx - b * ux;
+    py = vy - b * uy;
+    return px * px + py * py;                               // norm (p)
+}
+
+/** Return true if two lines intersect within their limits and in
+    all cases including coplanar lines, set one or two points of
+    intersection
+
+    @param[in] first first line to compare for intercept
+    @param[in] second second line to compare for intercept
+    @param[out] tuple1 first intercept point if found
+    @param[out] tuple2 second intercept point if lines are coplanar
+    @param[in] inFirstSegment true if the line intercept must be within
+                              the bounds of the first line. False if the
+                              first line should be considered as infinite
+    @param[in] inSecondSegment true if the line intercept must be within
+                               the bounds of the first line. False if the
+                               second line should be considered as infinite
+    @return the number of intercept points                                  */
+
+int LineIntercept (Line* first, Line* second, strkPt* tuple1, strkPt* tuple2,
+                   bool inFirstSegment, bool inSecondSegment)
+{
+    double len1, len2, w2x, w2y, t0, t1;
+    double dx1 = first->end.x - first->begin.x;
+    double dy1 = first->end.y - first->begin.y;
+    double dx2 = second->end.x - second->begin.x;
+    double dy2 = second->end.y - second->begin.y;
+    double wx  = first->begin.x - second->begin.x;
+    double wy  = first->begin.y - second->begin.y;
+
+    // Calculate the perpendicular product, dt
+
+    double dt = dx1 * dy2 - dy1 * dx2;
+
+    // Are the lines parallel?
+
+    if (fabs (dt) <= 1e-8)
+    {
+        // Check to see if they are overlap
+
+        if (dx1 * wy - dy1 * wx != 0 || dx2 * wy - dy2 * wx != 0) return 0;
+
+        // The line are coplanar, so check to see if they are degenerate
+
+        len1 = dx1 * dx1 + dy1 * dy1;
+        len2 = dx2 * dx2 + dy2 * dy2;
+
+        // First, check to see if both lines are points and if they are
+        // distinct tuples
+
+        if (len1 == 0 && len2 == 0)
+        {
+            if (first->begin.x != second->begin.x ||
+                first->begin.y != second->begin.y) return 0;
+            *tuple1 = first->begin;
+            return 1;
+        }
+
+        // Check to see if the first line is a point and is inside the
+        // second line
+
+        if (len1 == 0)
+        {
+            if (InLineSegment (second, &first->begin))
+                *tuple1 = first->begin;
+            else
+                return 0;
+        }
+
+        // Check to see if the second line is a point and is inside the
+        // first line
+
+        if (len2 == 0)
+        {
+            if (InLineSegment (first, &second->begin))
+                *tuple1 = second->begin;
+            else
+                return 0;
+        }
+
+        // Since both lines are coplanar and have length, search for
+        // overlap tuples
+
+        w2x = first->end.x - second->begin.x;
+        w2y = first->end.y - second->begin.y;
+
+        t0 = (dx2 != 0) ? wx  / dx2 : wy  / dy2;
+        t1 = (dx2 != 0) ? w2x / dx2 : w2y / dy2;
+
+        if (t0 > t1) SwapDouble (&t0, &t1);
+        if ((inFirstSegment || inSecondSegment) && (t0 > 1 || t1 < 0))
+        {
+            return 0;                       // No overlap
+        }
+        t0 = (t0 < 0) ? 0 : t0;             // Clip to min 0
+        t1 = (t1 > 1) ? 1 : t1;             // Clip to max 1
+
+        // Set the first tuple of intersection
+
+        tuple1->x = second->begin.x + t0 * dx2;
+        tuple1->y = second->begin.y + t0 * dy2;
+
+        // Check to see if the intersection is a single tuple
+
+        if (t0 == t1) return 1;
+
+        // Otherwise, set the second tuple of intersection as well
+
+        tuple2->x = second->begin.x + t1 * dx2;
+        tuple2->y = second->begin.y + t1 * dy2;
+        return 2;
+    }
+
+    // The segments are skew and may intersect in a tuple.
+    // Get the intersect parameter for the first line
+
+    double i1 = (dx2 * wy - dy2 * wx) / dt;
+    if (inFirstSegment && (i1 < 0 || i1 > 1)) return 0;
+
+    // Get the intersect parameter for the second line
+
+    double i2 = (dx1 * wy - dy1 * wx) / dt;
+    if (inSecondSegment && (i2 < 0 || i2 > 1)) return 0;
+
+    tuple1->x = first->begin.x + i1 * dx1;
+    tuple1->y = first->begin.y + i1 * dy1;
+    return 1;
+}
+
+/** Clip the line between (0,0) and the specified upper bound
+
+    @param[in,out] line line to be clipped within the bounds
+    @param[in] numCols maximum X (columns) for the line
+    @param[in] numRows maximum Y (rows) for the line
+    @return true if line overlaps the clip boundaries           */
+
+bool LineClip (Line *line, int numCols, int numRows)
+{
+    unsigned int i, found = 0;
+    Line boundLine, clipLine;
+    strkPt tuple1, tuple2, vertices[4];
+    vertices[0].x = 0;       vertices[0].y = 0;
+    vertices[1].x = numCols; vertices[1].y = 0;
+    vertices[2].x = numCols; vertices[2].y = numRows;
+    vertices[3].x = 0;       vertices[3].y = numRows;
+
+    clipLine.begin.x = 0;
+    clipLine.begin.y = 0;
+    clipLine.end.x = 0;
+    clipLine.end.y = 0;
+
+    for (i = 0; i < 4 && found < 2; ++i)
+    {
+        boundLine.begin = vertices[i];
+        boundLine.end   = vertices[(i + 1) % 4];
+        if (LineIntercept (line, &boundLine, &tuple1, &tuple2, false, true))
+        {
+            if (found == 0)
+            {
+                clipLine.begin = tuple1;
+                ++found;
+            }
+            else if (tuple1.x != clipLine.begin.x ||
+                     tuple1.y != clipLine.begin.y)
+            {
+                clipLine.end = tuple1;
+                ++found;
+            }
+        }
+    }
+
+    // If two endpoints are found, clip the line
+
+    if (found > 1)
+    {
+        if (clipLine.begin.x <= clipLine.end.x)
+        {
+            line->begin = clipLine.begin;
+            line->end   = clipLine.end;
+        }
+        else
+        {
+            line->begin = clipLine.end;
+            line->end   = clipLine.begin;
+        }
+    }
+    return found > 1;
+}
+
+/** Clip the line between (minX,minY) and (maxX,maxY)
+
+    @param[in,out] line line to be clipped within the bounds
+    @param[in] minX minimum X (columns) for the line
+    @param[in] minY minimum Y (rows) for the line
+    @param[in] maxX maximum X (columns) for the line
+    @param[in] maxY maximum Y (rows) for the line
+    @return true if line overlaps the clip boundaries           */
+
+bool LineClipFull (Line *line, int minX, int minY, int maxX, int maxY)
+{
+    unsigned int i, found = 0;
+    Line boundLine, clipLine;
+    strkPt tuple1, tuple2, vertices[4];
+    vertices[0].x = minX; vertices[0].y = minY;
+    vertices[1].x = maxX; vertices[1].y = minY;
+    vertices[2].x = maxX; vertices[2].y = maxY;
+    vertices[3].x = minX; vertices[3].y = maxY;
+
+    clipLine.begin.x = 0;
+    clipLine.begin.y = 0;
+    clipLine.end.x = 0;
+    clipLine.end.y = 0;
+
+    for (i = 0; i < 4 && found < 2; ++i)
+    {
+        boundLine.begin = vertices[i];
+        boundLine.end   = vertices[(i + 1) % 4];
+        if (LineIntercept (line, &boundLine, &tuple1, &tuple2, false, true))
+        {
+            if (found == 0)
+            {
+                clipLine.begin = tuple1;
+                ++found;
+            }
+            else if (tuple1.x != clipLine.begin.x ||
+                     tuple1.y != clipLine.begin.y)
+            {
+                clipLine.end = tuple1;
+                ++found;
+            }
+        }
+    }
+
+    // If two endpoints are found, clip the line
+
+    if (found > 1)
+    {
+        if (clipLine.begin.x <= clipLine.end.x)
+        {
+            line->begin = clipLine.begin;
+            line->end   = clipLine.end;
+        }
+        else
+        {
+            line->begin = clipLine.end;
+            line->end   = clipLine.begin;
+        }
+    }
+    return found > 1;
+}
+
+/** Move a line by the specified X and Y offsets
+    @param[in,out] line Line to move by X and Y offsets
+    @param[in] xOffset X shift applied to both endpoints
+    @param[in] yOffset Y shift applied to both endpoints        */
+
+void LineMove (Line *line, double xOffset, double yOffset)
+{
+    line->begin.x += xOffset;
+    line->begin.y += yOffset;
+    line->end.x   += xOffset;
+    line->end.y   += yOffset;
+}
+
+/** Return the maximum bounds between the line endpoints and
+    current bounds
+    @param[in] line Line endpoints to compare
+    @param[in,out] xMin minimum X value to update
+    @param[in,out] xMax maximum X value to update
+    @param[in,out] yMin minimum Y value to update
+    @param[in,out] yMax maximum Y value to update               */
+
+void MaxBounds (Line *line, int *xMin, int *xMax,  int *yMin, int *yMax)
+{
+    if (line->begin.x < *xMin) *xMin = (int) floor (line->begin.x);
+    if (line->end.x   < *xMin) *xMin = (int) floor (line->end.x);
+    if (line->begin.y < *yMin) *yMin = (int) floor (line->begin.y);
+    if (line->end.y   < *yMin) *yMin = (int) floor (line->end.y);
+
+    if (line->begin.x > *xMax) *xMax = (int) ceil (line->begin.x);
+    if (line->end.x   > *xMax) *xMax = (int) ceil (line->end.x);
+    if (line->begin.y > *yMax) *yMax = (int) ceil (line->begin.y);
+    if (line->end.y   > *yMax) *yMax = (int) ceil (line->end.y);
+}
+
+/** Map a line to an image for its specified width and store as
+    a list of pixel positions
+
+    @param[out] pixels list of PixelPos pointers corresponding
+                       based on the line settings
+    @param[in] line Line to map to pixels
+    @param[in] numCols maximum X (columns) for the line
+    @param[in] numRows maximum Y (rows) for the line            */
+
+void PixelsFromLine (StreakPixels* pixels, Line *line, int numCols, int numRows)
+{
+    Line offsetLine;
+    double slope, xOffset, yOffset, xMid, yMid;
+    int x, y, xBegin = numCols, yBegin = numRows, xEnd = 0, yEnd = 0;
+
+    // Extract the endpoints
+
+    double x1 = line->begin.x;
+    double y1 = line->begin.y;
+    double x2 = line->end.x;
+    double y2 = line->end.y;
+
+    // Determine the width and height of the line
+
+    double dx = x2 - x1;
+    double dy = y2 - y1;
+    double dr = sqrt (dx * dx + dy * dy);
+    double halfWidth  = line->width / 2.0;
+    if (!dr) return;
+
+    // Compute the intercepts of line width bounds and determine maximum
+    // bounds in each axis
+
+    xOffset = -halfWidth * dy / dr;
+    yOffset =  halfWidth * dx / dr;
+
+    offsetLine = *line;
+    LineMove (&offsetLine, xOffset, yOffset);
+    if (LineClip (&offsetLine, numCols, numRows))
+        MaxBounds (&offsetLine, &xBegin, &xEnd, &yBegin, &yEnd);
+
+    offsetLine = *line;
+    LineMove (&offsetLine, -xOffset, -yOffset);
+    if (LineClip (&offsetLine, numCols, numRows))
+        MaxBounds (&offsetLine, &xBegin, &xEnd, &yBegin, &yEnd);
+
+    // Step point by point based on the dominate axis
+
+    if (fabs (dx) > fabs (dy))
+    {
+        // ---------------------
+        // horizontal(ish) lines
+        // ---------------------
+        // If line is back to front, reorder endpoints and recompute
+        // the line width and height
+
+        if (x1 > x2)
+        {
+            SwapDouble (&x1, &x2);
+            SwapDouble (&y1, &y2);
+            dx = x2 - x1;
+            dy = y2 - y1;
+        }
+
+        // Compute the slope of the line
+
+        slope = dy / dx;
+
+        // Compute the x and y offsets for the line width extent
+
+        if (xBegin > xEnd)
+            SwapInt (&xBegin, &xEnd);
+        else
+            ++xEnd;
+        if (xBegin < 0) xBegin = 0;
+        if (xEnd > numCols) xEnd = numCols;
+
+        yMid = y1 + slope * (xBegin - x1);
+        yOffset = fabs (halfWidth * dr / dx);
+
+        for (x = xBegin; x != xEnd; ++x)
+        {
+            yBegin = (int) floor (yMid - yOffset);
+            yEnd   = (int) ceil  (yMid + yOffset) + 1;
+            for (y = yBegin; y != yEnd; ++y)
+            {
+                if (y >= 0 && y < numRows)
+                {
+                    psImageSet(pixels, x, y, 1);
+                }
+            }
+            yMid += slope;
+        }
+    }
+    else
+    {
+        // -------------------
+        // vertical(ish) lines
+        // -------------------
+        // If line is back to front, reorder endpoints and recompute
+        // the line width and height
+
+        if (y1 > y2)
+        {
+            SwapDouble (&x1, &x2);
+            SwapDouble (&y1, &y2);
+            dx = x2 - x1;
+            dy = y2 - y1;
+        }
+
+        // Compute the slope of the line
+
+        slope = dx / dy;
+
+        // Compute the x and y offsets for the line width extent
+
+        if (yBegin > yEnd)
+            SwapInt (&yBegin, &yEnd);
+        else
+            ++yEnd;
+        if (yBegin < 0) yBegin = 0;
+        if (yEnd > numRows) yEnd = numRows;
+
+        xMid = x1 + slope * (yBegin - y1);
+        xOffset = fabs (halfWidth * dr / dy);
+
+        for (y = yBegin; y != yEnd; ++y)
+        {
+            xBegin = (int) floor (xMid - xOffset);
+            xEnd   = (int) ceil  (xMid + xOffset) + 1;
+            for (x = xBegin; x != xEnd; ++x)
+            {
+                if (x >=0 && x < numCols)
+                {
+                    psImageSet(pixels, x, y, 1);
+                }
+            }
+            xMid += slope;
+        }
+    }                                       // if (fabs (dx) > fabs (dy))
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/Line.h
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/Line.h	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/Line.h	(revision 41117)
@@ -0,0 +1,46 @@
+#ifndef STREAK_LINE_H
+#define STREAK_LINE_H
+
+#include "streaksextern.h"
+
+/** Define a line object converted from streak equatorial object */
+
+typedef struct {
+    strkPt begin;
+    strkPt end;
+    double width;
+} Line;
+
+/** Clip the line between (0,0) and the specified upper bound
+
+    @param[in,out] line line to be clipped within the bounds
+    @param[in] numCols maximum X (columns) for the line
+    @param[in] numRows maximum Y (rows) for the line
+    @return true if line overlaps the clip boundaries           */
+
+extern bool LineClip (Line *line, int numCols, int numRows);
+
+/** Clip the line between (minX,minY) and (maxX,maxY)
+
+    @param[in,out] line line to be clipped within the bounds
+    @param[in] minX minimum X (columns) for the line
+    @param[in] minY minimum Y (rows) for the line
+    @param[in] maxX maximum X (columns) for the line
+    @param[in] maxY maximum Y (rows) for the line
+    @return true if line overlaps the clip boundaries           */
+
+extern bool LineClipFull (Line *line, int minX, int minY, int maxX, int maxY);
+
+/** Map a line to an image for its specified width and append as
+    a list of pixel positions
+
+    @param[out] pixels list of PixelPos pointers corresponding
+                       based on the line settings
+    @param[in] line Line to map to pixels   
+    @param[in] numCols maximum X (columns) for the line
+    @param[in] numRows maximum Y (rows) for the line            */
+
+extern void PixelsFromLine (StreakPixels* pixels, Line *line,
+                            int numCols, int numRows);
+
+#endif /* STREAK_LINE_H */
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/Makefile.am
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/Makefile.am	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/Makefile.am	(revision 41117)
@@ -0,0 +1,81 @@
+
+# Force recompilation of streaksVersion.c, since it gets the version information
+streaksVersion.c: streaksVersionDefinitions.h
+streaksVersionDefinitions.h: streaksVersionDefinitions.h.in FORCE
+	pslib-setsvnversion.pl STREAKS streaksVersionDefinitions.h.in streaksVersionDefinitions.h
+FORCE: ;
+
+bin_PROGRAMS = streaksremove streaksrelease streaksreplace streakscompare isdestreaked
+
+STREAKSREMOVE_CFLAGS=-DSTREAKS_COMPRESS_OUTPUT=1
+
+STREAKSCFLAGS = $(STREAKSREMOVE_CFLAGS) $(PSMODULE_CFLAGS) $(PSLIB_CFLAGS) ${NEBCLIENT_CFLAGS}
+STREAKSLDFLAGS = $(STREAKSREMOVE_LIBS) $(PSMODULE_LIBS) $(PSLIB_LIBS) ${NEBCLIENT_LIBS}
+
+streaksremove_CFLAGS = $(STREAKSCFLAGS)
+streaksremove_LDFLAGS = $(STREAKSLDFLAGS)
+streaksreplace_CFLAGS = $(STREAKSCFLAGS)
+streaksreplace_LDFLAGS = $(STREAKSLDFLAGS)
+streaksrelease_CFLAGS = $(STREAKSCFLAGS)
+streaksrelease_LDFLAGS = $(STREAKSLDFLAGS)
+streakscompare_CFLAGS = $(STREAKSCFLAGS)
+streakscompare_LDFLAGS = $(STREAKSLDFLAGS)
+isdestreaked_CFLAGS = $(STREAKSCFLAGS)
+isdestreaked_LDFLAGS = $(STREAKSLDFLAGS)
+
+COMMON_SOURCES = 	\
+	streaksio.c 	\
+	streaksutil.c 	\
+	streaksastrom.c \
+	streaksVersion.c
+
+streaksremove_SOURCES = \
+	streaksremove.c	\
+	streaksextern.c	\
+	diffedpixels.c	\
+	Line.c		\
+	$(COMMON_SOURCES)
+
+streaksrelease_SOURCES = \
+	streaksrelease.c\
+	streaksio.c	\
+	streaksutil.c	\
+	streaksastrom.c \
+	streaksVersion.c
+
+streaksreplace_SOURCES = \
+	streaksreplace.c\
+	streaksio.c	\
+	streaksutil.c	\
+	streaksastrom.c \
+	streaksVersion.c
+
+streakscompare_SOURCES = \
+	streakscompare.c\
+	streaksio.c	\
+	streaksutil.c	\
+	streaksastrom.c \
+	streaksVersion.c
+
+isdestreaked_SOURCES = \
+	isdestreaked.c\
+	streaksio.c	\
+	streaksutil.c	\
+	streaksastrom.c \
+	streaksVersion.c
+
+include_HEADERS = 	\
+	streaksremove.h \
+	streaksastrom.h \
+	streaksextern.h \
+	streaksio.h 	\
+	streaksVersionDefinitions.h
+
+clean-local:
+	-rm -f TAGS
+	-rm -f tags
+
+# Tags for emacs and vi
+tags:
+	etags `find . -name \*.[ch] -print`
+	ctags `find . -name \*.[ch] -print`
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/Makefile.simple
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/Makefile.simple	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/Makefile.simple	(revision 41117)
@@ -0,0 +1,87 @@
+# skeleton Makefile for streaksremove
+#
+# The intent is that these programs may be built against an
+# IPP installation outside the IPP build enviornment.
+
+COMMON_OBJECTS = \
+	streaksio.o \
+	streaksutil.o \
+	streaksastrom.o
+
+REMOVE_OBJECTS=    \
+    streaksremove.o \
+    streaksextern.o \
+    diffedpixels.o \
+    Line.o
+
+REPLACE_OBJECTS=      \
+    streaksreplace.o
+
+COMPARE_OBJECTS=      \
+    streakscompare.o
+
+RELEASE_OBJECTS=      \
+    streaksrelease.o
+
+ISDESTREAKED_OBJECTS=      \
+    isdestreaked.o
+
+
+HEADERS= Line.h  streaksastrom.h  streaksextern.h  streaksio.h  streaksremove.h
+
+STREAKSFLAGS=-DSTREAKS_COMPRESS_OUTPUT=1
+OPTFLAGS= -g -O2 -Wall -Werror
+#OPTFLAGS= -g
+CFLAGS=`psmodules-config --cflags` `pslib-config --cflags` -std=gnu99 ${OPTFLAGS} ${STREAKSFLAGS}
+#CFLAGS=`psmodules-config --cflags` `pslib-config --cflags` -std=gnu99 ${OPTFLAGS}
+LDFLAGS=`psmodules-config --libs` `pslib-config --libs`
+
+PROGRAMS= streaksremove streaksreplace streakscompare streaksrelease isdestreaked
+HEADERS=Line.h streaksastrom.h streaksextern.h streaksio.h streaksremove.h
+
+all: ${PROGRAMS}
+
+# programs all depend on the common objects and the common headers
+PROGRAM_OBJECTS = \
+ ${REMOVE_OBJECTS} \
+ ${REPLACE_OBJECTS} \
+ ${COMPARE_OBJECTS} \
+ ${RELEASE_OBJECTS} \
+ ${ISDESTREAKED_OBJECTS}
+
+${COMMON_OBJECTS}: ${HEADERS}
+
+${PROGRAM_OBJECTS}: ${HEADERS} ${COMMON_OBJECTS}
+
+streaksremove:  ${REMOVE_OBJECTS} ${COMMON_OBJECTS}
+
+streaksreplace:  ${REPLACE_OBJECTS} ${COMMON_OBJECTS}
+
+streakscompare:  ${COMPARE_OBJECTS} ${COMMON_OBJECTS}
+
+streaksrelease:  ${RELEASE_OBJECTS} ${COMMON_OBJECTS}
+
+isdestreaked:	${ISDESTREAKED_OBJECTS} ${COMMON_OBJECTS}
+
+
+install:	${PROGRAMS}
+	install -t  $(PSCONFDIR)/$(PSCONFIG)/bin streaksremove
+	install -t  $(PSCONFDIR)/$(PSCONFIG)/bin streaksreplace
+	install -t  $(PSCONFDIR)/$(PSCONFIG)/bin streakscompare
+	install -t  $(PSCONFDIR)/$(PSCONFIG)/bin streaksrelease
+	install -t  $(PSCONFDIR)/$(PSCONFIG)/bin isdestreaked
+
+clean:
+	rm -f *.o ${PROGRAMS}
+    
+
+clean-local:
+	-rm -f TAGS
+
+# Tags for emacs
+tags:
+	etags `find . -name \*.[ch] -print`
+
+### Error codes.
+BUILT_SOURCES = 
+CLEANFILES = 
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/diffedpixels.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/diffedpixels.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/diffedpixels.c	(revision 41117)
@@ -0,0 +1,277 @@
+#include "streaksremove.h"
+#include "pmAstrometryWCS.h"
+
+#define DEBUG_PRINT 1
+
+static void addTouchedPixels(streakFiles *sf, psString fileName);
+static void nameCorners(psPlane pt[4]);
+static int xLeft(psPlane *pt, int y);
+static int xRight(psPlane *pt, int y);
+
+#define  BL 0
+#define  BR 1
+#define  TL 2
+#define  TR 3
+
+// Examine the set of diff skycells and compute the pixels that were
+// included in difference processing
+bool
+computeDiffedPixels(streakFiles *sf)
+{
+    bool status;
+    pmConfig    *config = sf->config;
+
+    psArray *skycells = psMetadataLookupPtr(&status, config->arguments, "SKYCELLS");
+    if (skycells == NULL) {
+        return false;
+    }
+
+    int n = psArrayLength(skycells);
+
+    psRegion     *bounds = pmChipPixels(sf->chip);
+
+    int width  = bounds->x1 - bounds->x0;
+    int height = bounds->y1 - bounds->y0;
+
+    psFree(bounds);
+
+    sf->diffedPixels = psImageAlloc(width, height, PS_TYPE_U8);
+    psImageInit(sf->diffedPixels, 0);
+
+    for (int i=0; i<n; i++) {
+        psString filename = psArrayGet(skycells, i);
+        psString resolved_name = pmConfigConvertFilename(filename, config, false, false);
+        if (resolved_name == NULL) {
+            psError(PS_ERR_IO, false, "failed to resolve skycell file: %s", filename);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        addTouchedPixels(sf, resolved_name);
+        psFree(resolved_name);
+    }
+
+    bool writeTouchedPixels = false; // XXX: make this an argument to the program
+    if (writeTouchedPixels) {
+        // write out the diffed pixels
+        psMetadata * header = psMetadataAlloc();
+        if (sf->inAstrom->fpa->toSky->type != PS_PROJ_DIS) {
+            pmAstromWriteWCS(header, sf->inAstrom->fpa, sf->chip, 0.001);
+        }
+        psFits *fits = psFitsOpen("diffedpixels.fits", "w");
+
+        psFitsWriteImage(fits, header, sf->diffedPixels, 0, NULL);
+        psFitsClose(fits);
+        psFree(header);
+    }
+    return true;
+}
+
+static void
+addTouchedPixels(streakFiles *sf, psString fileName)
+{
+    // read fits header
+    psFits *fits = psFitsOpen(fileName, "r");
+    if (!fits) {
+        psError(PS_ERR_IO, false, "failed to open skycell file: %s", fileName);
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+    psMetadata *header = psFitsReadHeader(NULL, fits);
+    psFitsClose(fits);
+    if (!header) {
+        psError(PS_ERR_IO, false, "failed to read fixts header from skycell file: %s", fileName);
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+    // set up astrometry for conversion from skycell image to sky
+    pmAstromWCS *wcs = pmAstromWCSfromHeader(header);
+    if (!wcs) {
+        psError(PS_ERR_IO, false, "failed to read astrometry  from skycell file: %s", fileName);
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+    int naxis1 = psMetadataLookupS32(NULL, header, "NAXIS1");
+    int naxis2 = psMetadataLookupS32(NULL, header, "NAXIS2");
+
+    psFree(header);
+
+    /* now set up our wrapper to the chip astrometry to apply to the whole chip */
+    sf->astrom = streakSetAstrometry(sf->astrom, sf->stage, sf->inAstrom->fpa, sf->chip, false, NULL,
+        sf->diffedPixels->numCols, sf->diffedPixels->numRows);
+
+    // convert corners of skycell to chip coordinates
+    // compute overlap with the chip using astrometry
+    psPlane pt[4];
+    pt[0].x = 0;
+    pt[0].y = 0;
+
+    pt[1].x = naxis1 - 1;
+    pt[1].y = 0;
+
+    pt[2].x = naxis1 - 1;
+    pt[2].y = naxis2 - 1;
+
+    pt[3].x = 0;
+    pt[3].y = naxis2 - 1;
+
+    for (int i = 0; i < 4 ; i++ ) {
+        psSphere sky;
+
+        // convert corner of skycell to sky coordinates
+        if (!pmAstromWCStoSky(&sky, wcs, &pt[i])) {
+            psErrorStackPrint(stderr, "failed to convert pt %d of %s to sky coords", i, fileName);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        strkPt p;
+        // convert to chip coordinates
+        if (!skyToCell(&p, sf->astrom, sky.r, sky.d)) {
+            psErrorStackPrint(stderr, "failed to convert pt %d of %s to sky coords", i, fileName);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        pt[i].x = round(p.x);
+        pt[i].y = round(p.y);
+    }
+
+    psFree(wcs);
+
+    // Identify the bottom left, bottom right, top left, and top right corners
+    nameCorners(pt);
+
+#ifdef DEBUG_PRINT
+    fprintf(stderr, "\nSKYCELL %s\n", fileName);
+    fprintf(stderr, "BL %f %f\n", pt[BL].x, pt[BL].y);
+    fprintf(stderr, "BR %f %f\n", pt[BR].x, pt[BR].y);
+    fprintf(stderr, "TL %f %f\n", pt[TL].x, pt[TL].y);
+    fprintf(stderr, "TR %f %f\n", pt[TR].x, pt[TR].y);
+#endif
+
+    int xmax = sf->diffedPixels->numCols - 1;
+    int ymin;
+    int ymax; 
+    
+    if (pt[BL].y <= pt[BR].y) {
+        ymin = fmax(0, pt[BL].y);
+    } else {
+        ymin = fmax(0, pt[BR].y);
+    }
+    if (pt[TR].y >= pt[TL].y) {
+        ymax = fmin(pt[TR].y, sf->diffedPixels->numRows - 1);
+    } else {
+        ymax = fmin(pt[TL].y, sf->diffedPixels->numRows - 1);
+    }
+        
+#if (DEBUG_PRINT > 1)
+    fprintf(stderr, "\nxmin: %d xmax: %d\n", 0, xmax);
+    fprintf(stderr, "\nymin: %d ymax: %d\n", ymin, ymax);
+#endif
+
+    // Now set the touched pixels
+    for (int y = ymin ; y <= ymax; y++) {
+
+        int xleft  = xLeft(pt, y) - 1;
+        int xright = xRight(pt, y) + 1;
+
+        if (xright < 0) {
+            continue;
+        }
+        if (xleft > xmax) {
+            continue;
+        }
+        if (xleft < 0) {
+            xleft = 0;
+        }
+        if (xright > xmax) {
+            xright = xmax;
+        }
+#if (DEBUG_PRINT > 1)
+        fprintf(stderr, "  y: %d xleft: %d xright: %d\n", y, xleft, xright);
+#endif
+
+        psU8 *rowPixels = sf->diffedPixels->data.U8[y];
+
+        int n = xright - xleft + 1;
+        if (n > 0) {
+            memset(&rowPixels[xleft], 255, n);
+        }
+    }
+}
+
+// x as a function of y for the line between two points
+// Note: the caller guarentees that the y's of the two points are different
+static double xOfY(psPlane *pI, psPlane *pJ, int y)
+{
+    double m_inv = (double) (pJ->x - pI->x) / (double) (pJ->y - pI->y);
+
+    double x = ((double) y - pI->y) * m_inv + (double) pI->x;
+
+    return x;
+}
+
+static int xLeft(psPlane *pt, int y)
+{
+    double x_d;
+
+    if ((y <= pt[BL].y ) && ((pt[BL].y >= pt[BR].y))) {
+        // left boundary is the line from bottom left to bottom right
+        x_d = xOfY(&pt[BL], &pt[BR], y);
+    } else if ( y <= pt[TL].y ) {
+        // left boundary is the line from Bottom left to top left
+        x_d = xOfY(&pt[BL], &pt[TL], y);
+    } else {
+        // left boundary is the line from top left to top right
+        x_d= xOfY(&pt[TL], &pt[TR], y);
+    }
+
+    return (int) floor(x_d);
+}
+
+static int xRight(psPlane *pt, int y)
+{
+    double x_d;
+    if ((y <= pt[BR].y) && (pt[BR].y > pt[BL].y)) {
+        // right boundary is the line from Bottom right to Top right
+        x_d = xOfY(&pt[BL], &pt[BR], y);
+    } else if ( y<= pt[TR].y) {
+        // right boundary is the line from Top left to top right
+        x_d = xOfY(&pt[BR], &pt[TR], y);
+    } else {
+        x_d = xOfY(&pt[TL], &pt[TR], y);
+    }
+    return (int) ceil(x_d);
+}
+
+static void
+nameCorners(psPlane pt[4])
+{
+    // sort points top to bottom (in case of ties take favor rightmost)
+    int i;
+    int j;
+    for (i=0; i<4; i++) {
+        for (j = i + 1; j < 4; j++) {
+            if ((pt[j].y > pt[i].y) || 
+                ((pt[j].y == pt[i].y) && (pt[j].x > pt[i].x))) {
+                psPlane tmpPt = pt[j];
+                pt[j] = pt[i];
+                pt[i] = tmpPt;
+            }
+        }
+    }
+    psPlane bl, br;
+    // sort bottom two points in x
+    if (pt[3].x < pt[2].x) {
+        bl = pt[3];
+        br = pt[2];
+    } else {
+        bl = pt[2];
+        br = pt[3];
+    }
+    // sort the top two points in x
+    psPlane tl, tr;
+    if (pt[0].x < pt[1].x) {
+        tl = pt[0];
+        tr = pt[1];
+    } else {
+        tl = pt[1];
+        tr = pt[0];
+    }
+    pt[BL] = bl;
+    pt[BR] = br;
+    pt[TL] = tl;
+    pt[TR] = tr;
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/isdestreaked.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/isdestreaked.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/isdestreaked.c	(revision 41117)
@@ -0,0 +1,41 @@
+#include "streaksremove.h"
+
+char *streaksProgram = "isdestreaked";
+
+int
+main(int argc, char *argv[])
+{
+    pmConfig *config = pmConfigRead(&argc, argv, NULL);
+    if (!config) {
+        psErrorStackPrint(stderr, "failed to parse arguments\n");
+        exit(PS_EXIT_CONFIG_ERROR);
+    }
+
+    char *fileName = argv[1];
+    if (!fileName) {
+        fprintf(stderr, "Usage: %s filename\n", argv[0]);
+        exit(PS_EXIT_DATA_ERROR);
+    }
+
+    psFits *fits = psFitsOpen(fileName, "r");
+    if (!fits) {
+        psErrorStackPrint(stderr, "failed to open fits file: %s\n", fileName);
+        exit(PS_EXIT_DATA_ERROR);
+    }
+
+    psMetadata *header = psFitsReadHeader(NULL, fits);
+    if (!header) {
+        psErrorStackPrint(stderr, "failed to read header for: %s", fileName);
+        exit(PS_EXIT_DATA_ERROR);
+    }
+
+    bool mdok;
+    bool isDestreaked = psMetadataLookupBool(&mdok, header, "PSDESTRK");
+    if (mdok && isDestreaked) {
+        printf("%s is a destreaked file\n", fileName);
+        exit(0);
+    } else {
+        printf("%s is not a destreaked file\n", fileName);
+        exit(42);
+    }
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksVersion.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksVersion.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksVersion.c	(revision 41117)
@@ -0,0 +1,115 @@
+/** @file streaksVersion.c
+ *
+ *  @brief
+ *
+ *  @ingroup streaks
+ *
+ *  Copyright 2009 Institute for Astronomy, University of Hawaii
+ */
+
+#include "streaksremove.h"
+#include "streaksVersionDefinitions.h"
+
+#ifndef STREAKS_VERSION
+#error "STREAKS_VERSION is not set"
+#endif
+#ifndef STREAKS_BRANCH
+#error "STREAKS_BRANCH is not set"
+#endif
+#ifndef STREAKS_SOURCE
+#error "STREAKS_SOURCE is not set"
+#endif
+
+psString streaksVersion(void)
+{
+    char *value = NULL;
+    psStringAppend(&value, "%s@%s", STREAKS_BRANCH, STREAKS_VERSION);
+    return value;
+}
+
+psString streaksSource(void)
+{
+  return psStringCopy(STREAKS_SOURCE);
+}
+
+psString streaksVersionLong(void)
+{
+    psString version = streaksVersion();  // Version, to return
+    psString source = streaksSource();    // Source
+
+    psStringPrepend(&version, "%s ", streaksProgram);
+    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 streaksVersionHeader(psMetadata *header)
+{
+    PS_ASSERT_METADATA_NON_NULL(header, false);
+
+    psString version = streaksVersion(); // Software version
+    psString source = streaksSource();   // Software source
+
+    psMetadataAddStr(header, PS_LIST_TAIL, "STREAK_V", PS_META_REPLACE, NULL, source);
+    
+    psStringPrepend(&version, "%s version: ", streaksProgram);
+    psStringPrepend(&source, "%s source: ", streaksProgram);
+
+    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 streaksVersionHeaderFull(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, "%s at %s", streaksProgram, timeString);
+    psFree(timeString);
+    psMetadataAddStr(header, PS_LIST_TAIL, "HISTORY", PS_META_DUPLICATE_OK, NULL, history);
+    psFree(history);
+
+    psLibVersionHeader(header);
+    psModulesVersionHeader(header);
+    streaksVersionHeader(header);
+
+    return true;
+}
+
+void streaksVersionPrint(void)
+{
+    psTime *time = psTimeGetNow(PS_TIME_TAI); // The time now
+    psString timeString = psTimeToISO(time); // The time in an ISO string
+    psFree(time);
+    psLogMsg("streaks", PS_LOG_INFO, "%s at %s", streaksProgram, timeString);
+    psFree(timeString);
+
+    psString pslib = psLibVersionLong();// psLib version
+    psString psmodules = psModulesVersionLong(); // psModules version
+    psString streaks = streaksVersionLong(); // streaks version
+
+    psLogMsg("streaks", PS_LOG_INFO, "%s", pslib);
+    psLogMsg("streaks", PS_LOG_INFO, "%s", psmodules);
+    psLogMsg("streaks", PS_LOG_INFO, "%s", streaks);
+
+    psFree(pslib);
+    psFree(psmodules);
+    psFree(streaks);
+
+    return;
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksVersionDefinitions.h.in
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksVersionDefinitions.h.in	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksVersionDefinitions.h.in	(revision 41117)
@@ -0,0 +1,8 @@
+#ifndef STREAKS_VERSION_DEFINITIONS_H
+#define STREAKS_VERSION_DEFINITIONS_H
+
+#define STREAKS_VERSION @STREAKS_VERSION@ // SVN version
+#define STREAKS_BRANCH  @STREAKS_BRANCH@  // SVN branch
+#define STREAKS_SOURCE  @STREAKS_SOURCE@  // SVN source
+
+#endif
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksastrom.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksastrom.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksastrom.c	(revision 41117)
@@ -0,0 +1,436 @@
+#include "streaksremove.h"
+
+static void freeAstrometry(strkAstrom *astrom)
+{
+    if (!astrom) {
+        return;
+    }
+    psFree(astrom->pt);
+}
+
+// Initialize the astrometry object for current cell
+strkAstrom *
+streakSetAstrometry(strkAstrom *astrom, ippStage stage, pmFPA *fpa, pmChip *chip, bool fromCell, psMetadata *md, int numCols, int numRows)
+{
+    if (!astrom) {
+        astrom = psAlloc(sizeof(strkAstrom));
+        psMemSetDeallocator(astrom, (psFreeFunc) freeAstrometry);
+        astrom->pt = pmAstromObjAlloc();
+    }
+    astrom->fpa = fpa;
+    astrom->chip = chip;
+
+    int cell_x0, cell_y0;
+    int xParityCell, yParityCell;
+    bool mdok;
+
+    if (fromCell) {
+        // XXX: I think that this is not needed
+        // We are only asked to get the concepts from the cell when we're reading from a pmfile (skycell)
+        // In that case we (x0,y0) = (0,0) and the parirties are positive
+        // If the x0, y0 are not zero they are probably in the FITS unity based array format
+        // We should probably assert that these are zero
+        cell_x0 =  psMetadataLookupS32(&mdok, md, "CELL.X0");
+        if (!mdok) {
+            psError(PS_ERR_UNKNOWN, true, "CELL.X0 for cell is not set.\n");
+            return NULL;
+        }
+        cell_y0 = psMetadataLookupS32(&mdok, md, "CELL.Y0");
+        if (!mdok) {
+            psError(PS_ERR_UNKNOWN, true, "CELL.Y0 for cell is not set.\n");
+            return NULL;
+        }
+        xParityCell = psMetadataLookupS32(&mdok, md, "CELL.XPARITY");
+        if (!mdok || (xParityCell != 1 && xParityCell != -1)) {
+            psError(PS_ERR_UNKNOWN, true, "CELL.XPARITY for cell is not set.\n");
+            return false;
+        }
+        yParityCell = psMetadataLookupS32(&mdok, md, "CELL.YPARITY");
+        if (!mdok || (yParityCell != 1 && yParityCell != -1)) {
+            psError(PS_ERR_UNKNOWN, true, "CELL.YPARITY for cell is not set.\n");
+            return false;
+        }
+    } else if (md && (stage == IPP_STAGE_RAW)) {
+        // The metadata is the raw header
+        // Assumes GPC1
+        //
+        // Shouldn't these lookups be F32 ?
+        cell_x0 =  psMetadataLookupS32(&mdok, md, "IMNPIX1");
+        if (!mdok) {
+            psError(PS_ERR_UNKNOWN, true, "IMNPIX1 for cell is not set.\n");
+            // return NULL;
+        }
+        cell_y0 = psMetadataLookupS32(&mdok, md, "IMNPIX2");
+        if (!mdok) {
+            psError(PS_ERR_UNKNOWN, true, "IMNPIX2 for cell is not set.\n");
+            return NULL;
+        }
+
+        xParityCell = psMetadataLookupS32(&mdok, md, "ATM1_1");
+        if (!mdok || (xParityCell != 1 && xParityCell != -1)) {
+            psError(PS_ERR_UNKNOWN, true, "CELL.XPARITY for cell is not set.\n");
+            return false;
+        }
+        yParityCell = psMetadataLookupS32(&mdok, md, "ATM2_2");
+        if (!mdok || (yParityCell != 1 && yParityCell != -1)) {
+            psError(PS_ERR_UNKNOWN, true, "CELL.YPARITY for cell is not set.\n");
+            return false;
+        }
+    } else {
+        // no metadata regular cell
+        // used by warpedpixels
+        cell_x0 = 0;
+        cell_y0 = 0;
+        xParityCell = 1;
+        yParityCell = 1;
+    }
+
+    astrom->cell_x0 = (double) cell_x0;
+    astrom->cell_y0 = (double) cell_y0;
+    // XXX: make sure we're applying cell parity correctly
+    // the mosaic code uses (xParityCell * xParityChip == xParityTarget) ? -1 : 1
+    //                            (yParityCell * yParityChip == yParityTarget) ? -1 : 1
+    // since the transform already accounts for the chip parity we just use the cell parity
+    astrom->xParity = (double) xParityCell;
+    astrom->yParity = (double) yParityCell;
+
+    astrom->numCols = numCols;
+    astrom->numRows = numRows;
+
+    return astrom;
+}
+
+
+
+void
+chipToCell(double *xCell, double *yCell, strkAstrom *astrom, double xChip, double yChip)
+{
+    // convert from chip to cell
+    if (astrom->xParity > 0) {
+        *xCell = xChip - astrom->cell_x0;
+    } else {
+        *xCell = astrom->cell_x0 - 1 - xChip;
+    }
+    if (astrom->yParity > 0) {
+        *yCell = yChip - astrom->cell_y0;
+    } else {
+        *yCell = astrom->cell_y0 - 1 - yChip;
+    }
+}
+
+void
+cellToChip(double *xChip, double *yChip, strkAstrom *astrom, double xCell, double yCell)
+{
+    if (astrom->xParity > 0) {
+        *xChip = xCell + astrom->cell_x0;
+    } else {
+        *xChip = astrom->cell_x0 - 1 - xCell;
+    }
+    if (astrom->yParity > 0) {
+        *yChip = yCell + astrom->cell_y0;
+    } else {
+        *yChip =  astrom->cell_y0 - 1 - yCell;
+    }
+}
+
+void
+cellToChipInt(unsigned int *xChip, unsigned int *yChip, strkAstrom *astrom, int xCell, int yCell)
+{
+    if (astrom->xParity > 0) {
+        *xChip = xCell + astrom->cell_x0;
+    } else {
+        *xChip = astrom->cell_x0 - 1 - xCell;
+    }
+    if (astrom->yParity > 0) {
+        *yChip = yCell + astrom->cell_y0;
+    } else {
+        *yChip =  astrom->cell_y0 - 1 - yCell;
+    }
+}
+
+bool
+SkyToLocal(strkPt *outPt, strkAstrom *astrom, double ra, double dec)
+{
+    // generate a local project using the RA, DEC of the 0,0 pixel of the chip as the
+    // projection center with the same plate scale as the nominal TP->Sky astrometry.
+
+    pmFPA *fpa   = (pmFPA *) astrom->fpa;
+
+    // find the RA,DEC coords of the 0,0 pixel for this chip:
+
+    psPlane ptTP;
+    psSphere ptSky;
+
+    ptSky.r = ra;
+    ptSky.d = dec;
+    ptSky.rErr = 0.0;
+    ptSky.dErr = 0.0;
+
+    psProject(&ptTP, &ptSky, fpa->toSky);
+
+    outPt->x = ptTP.x;
+    outPt->y = ptTP.y;
+
+    return true;
+}
+
+bool
+LocalToSky(strkPt *outPt, strkAstrom *astrom, strkPt *inPt)
+{
+    // generate a local project using the RA, DEC of the 0,0 pixel of the chip as the
+    // projection center with the same plate scale as the nominal TP->Sky astrometry.
+
+    pmFPA *fpa   = (pmFPA *) astrom->fpa;
+
+    // find the RA,DEC coords of the 0,0 pixel for this chip:
+
+    psPlane ptTP;
+    psSphere ptSky;
+
+    ptTP.x = inPt->x;
+    ptTP.y = inPt->y;
+    ptTP.xErr = 0.0;
+    ptTP.yErr = 0.0;
+
+    psDeproject(&ptSky, &ptTP, fpa->toSky);
+
+    outPt->x = ptSky.r;
+    outPt->y = ptSky.d;
+
+    return true;
+}
+
+bool
+componentBounds(int *minX, int *minY, int *maxX, int *maxY, strkAstrom *astrom, int numCols, int numRows)
+{
+    // find the bounds of the (padded) chip region in tangent-plane coordinates
+
+    pmFPA *fpa   = (pmFPA *) astrom->fpa;
+    pmChip *chip = (pmChip *) astrom->chip;
+
+    psPlane ptCH, ptFP, TPo, TPx, TPy;
+
+    // coordinate of the chip center:
+    ptCH.x = 0.5*numCols;
+    ptCH.y = 0.5*numRows;
+    psPlaneTransformApply(&ptFP, chip->toFPA, &ptCH);
+    psPlaneTransformApply(&TPo, fpa->toTPA,  &ptFP);
+
+    // coordinate of the chip center + dX/2:
+    ptCH.x = numCols;
+    ptCH.y = 0.5*numRows;
+    psPlaneTransformApply(&ptFP, chip->toFPA, &ptCH);
+    psPlaneTransformApply(&TPx, fpa->toTPA,  &ptFP);
+
+    // coordinate of the chip center + dY/2:
+    ptCH.x = 0.5*numCols;
+    ptCH.y = numRows;
+    psPlaneTransformApply(&ptFP, chip->toFPA, &ptCH);
+    psPlaneTransformApply(&TPy, fpa->toTPA,  &ptFP);
+
+    // half-lengths of the two sides in tangent-plane coords:
+    double xSize = hypot (TPx.x - TPo.x, TPx.y - TPo.y);
+    double ySize = hypot (TPy.x - TPo.x, TPy.y - TPo.y);
+    double radius = hypot (xSize, ySize);
+
+    // define the region encompassed by the radius with some padding:
+    *minX = TPo.x - 1.1*radius;
+    *minY = TPo.y - 1.1*radius;
+    *maxX = TPo.x + 1.1*radius;
+    *maxY = TPo.y + 1.1*radius;
+
+    return true;
+}
+
+bool
+skyToCell(strkPt *outPt, strkAstrom *astrom, double ra, double dec)
+{
+    pmFPA *fpa   = (pmFPA *) astrom->fpa;
+    pmChip *chip = (pmChip *) astrom->chip;
+    pmAstromObj *pt = (pmAstromObj *) astrom->pt;
+
+    pt->sky->r = ra;
+    pt->sky->d = dec;
+    pt->sky->rErr = 0;  // Is setting these errors to zero the right thing to do?
+    pt->sky->dErr = 0;
+
+    // convert from sky to TP to FP
+    psProject(pt->TP, pt->sky, fpa->toSky);
+    psPlaneTransformApply(pt->FP, fpa->fromTPA, pt->TP);
+    // convert from FP to chip
+    psPlaneTransformApply(pt->chip, chip->fromFPA, pt->FP);
+
+    chipToCell(&outPt->x, &outPt->y, astrom, pt->chip->x, pt->chip->y);
+
+//    printf("cell: %f %f chip: %f %f\n", outPt->x, outPt->y, pt->chip->x, pt->chip->y);
+
+    return true;
+}
+
+bool
+cellToSky(strkPt *outPt, strkAstrom *astrom, double x, double y)
+{
+    pmFPA *fpa   = (pmFPA *) astrom->fpa;
+    pmChip *chip = (pmChip *) astrom->chip;
+    pmAstromObj *pt = (pmAstromObj *) astrom->pt;
+
+    cellToChip(&pt->chip->x, &pt->chip->y, astrom, x, y);
+
+    pt->chip->xErr = 0;  // Is setting these errors to zero the right thing to do?
+    pt->chip->yErr = 0;
+
+        // chip to FP
+    psPlaneTransformApply(pt->FP, chip->toFPA, pt->chip);
+    // FP to TP to sky
+    psPlaneTransformApply(pt->TP, fpa->toTPA, pt->FP);
+    psDeproject(pt->sky, pt->TP, fpa->toSky);
+
+    outPt->x = pt->sky->r;
+    outPt->y = pt->sky->d;
+
+    return true;
+}
+
+
+static bool
+readAstrometry(streakFiles *sf)
+{
+    pmHDU *phu = pmFPAviewThisPHU(sf->view, sf->inAstrom->fpa);
+    if (phu) {
+        bool status;
+        char *ctype = psMetadataLookupStr(&status, phu->header, "CTYPE1");
+        if (ctype) {
+            sf->bilevelAstrometry = !strcmp (&ctype[4], "-DIS");
+        }
+    }
+
+    pmHDU *hdu = pmFPAviewThisHDU(sf->view, sf->inAstrom->fpa);
+    PS_ASSERT_PTR_NON_NULL(hdu, 1)
+    PS_ASSERT_PTR_NON_NULL(hdu->header, 1)
+    if (sf->bilevelAstrometry) {
+        if (!pmAstromReadBilevelMosaic(sf->inAstrom->fpa, phu->header)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read bilevel mosaic astrometry for input FPA.");
+            return false;
+        }
+       if (!pmAstromReadBilevelChip (sf->chip, hdu->header)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read bilevel chip astrometry for input FPA.");
+            return false;
+        }
+    } else {
+
+        // we use a default FPA pixel scale of 1.0
+        if (!pmAstromReadWCS (sf->inAstrom->fpa, sf->chip, hdu->header, 1.0)) {
+            psError(PS_ERR_UNKNOWN, false, "Unable to read WCS astrometry for input FPA.");
+            return false;
+        }
+    }
+
+    return true;
+}
+
+void
+setupAstrometry(streakFiles *sf)
+{
+    bool status;
+    // load astrometry file
+    if (USE_SUPPLIED_ASTROM(sf->stage)) {
+        sf->inAstrom = pmFPAfileDefineFromArgs(&status, sf->config, "PSWARP.ASTROM", "ASTROM");
+    } else {
+        // otherwise get astrometry from pmfile
+        if (!sf->inImage->pmfile) {
+            streaksExit("unexpected null pmFPAfile", PS_EXIT_CONFIG_ERROR);
+        }
+        sf->inAstrom = sf->inImage->pmfile;
+    }
+    sf->view = pmFPAviewAlloc(0);
+
+    if (!pmFPAfileIOChecks(sf->config, sf->view, PM_FPA_BEFORE)) {
+        psError(PS_ERR_UNKNOWN, false, "Failed to load input.");
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+
+    while ((sf->chip = pmFPAviewNextChip(sf->view, sf->inAstrom->fpa, 1)))  {
+        if (sf->inAstrom->fpa->chips->n == 1) {
+            // There's only one chip in this FPA and we've got it so break
+            break;
+        }
+        bool status;
+        psString chip_name = psMetadataLookupStr(&status, sf->chip->concepts, "CHIP.NAME");
+        if (!strcmp(chip_name, sf->class_id)) {
+            break;
+        }
+    }
+    if (!sf->chip) {
+        psError(PS_ERR_UNKNOWN, true, "Failed to find chip with data.");
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+    if (!pmFPAfileIOChecks(sf->config, sf->view, PM_FPA_BEFORE)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to load chip");
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+    if (!readAstrometry(sf)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to read astrometry");
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+}
+
+static void chipToSky(pmAstromObj *pt, pmFPA *fpa, pmChip *chip)
+{
+    // chip to FP
+    psPlaneTransformApply(pt->FP, chip->toFPA, pt->chip);
+    // FP to TP to sky
+    psPlaneTransformApply(pt->TP, fpa->toTPA, pt->FP);
+    psDeproject(pt->sky, pt->TP, fpa->toSky);
+}
+
+
+bool
+linearizeTransforms(strkAstrom *astrom)
+{
+    pmFPA  *inFPA  = (pmFPA *) astrom->fpa;
+    pmChip *inChip = (pmChip *) astrom->chip;
+
+    // compute ra and dec of chip center
+    pmAstromObj *center = pmAstromObjAlloc();
+    center->chip->x = (double) astrom->numCols / 2.;
+    center->chip->xErr = 0;
+    center->chip->y = (double) astrom->numRows / 2.;
+    center->chip->yErr = 0;
+
+    chipToSky(center, inFPA, inChip);
+
+    psRegion bounds = {0, astrom->numCols - 1, 0, astrom->numRows - 1 };
+
+    // create skeleton containers for the linearized transforms;
+    pmFPA  outFPA;
+    pmChip outChip;
+
+    memset(&outFPA, 0, sizeof(outFPA));
+    memset(&outChip, 0, sizeof(outChip));
+
+    outFPA.toSky = psProjectionAlloc (center->sky->r, center->sky->d, inFPA->toSky->Xs, inFPA->toSky->Ys, PS_PROJ_TAN);
+
+    psFree(center);
+
+    if (!pmAstromLinearizeToSky(inFPA, inChip, &outFPA, &outChip, &bounds)) {
+        psError(PS_ERR_UNKNOWN, false, "Failed to linearize astrometry\n");
+        return false;
+    }
+
+    psFree(inFPA->toSky);
+    inFPA->toSky = outFPA.toSky;
+
+    psFree(inFPA->toTPA);
+    inFPA->toTPA = outFPA.toTPA;
+
+    psFree(inFPA->fromTPA);
+    inFPA->fromTPA = outFPA.fromTPA;
+
+    psFree(inChip->toFPA)
+    inChip->toFPA = outChip.toFPA;
+
+    psFree(inChip->fromFPA);
+    inChip->fromFPA = outChip.fromFPA;
+
+    return true;
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksastrom.h
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksastrom.h	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksastrom.h	(revision 41117)
@@ -0,0 +1,38 @@
+#ifndef STREAKSASTROM_H
+#define STREAKSASTROM_H
+
+// Structure that encapsulates Astrometry
+// Note: this file should have no dependence on ipp data types
+// XXX: we might be able to remove this restrction. Paul S has
+// used some of our datatypes in his code
+//
+typedef struct {
+    // opaque pointers to psModules types
+    void    *pt;        // pmAstromObj *
+    void    *fpa;       // pmFPA *
+    void    *chip;      // pmChip *
+
+    double  cell_x0;
+    double  cell_y0;
+    double  xParity;    // 1 or -1
+    double  yParity;
+    int     numCols;
+    int     numRows;
+} strkAstrom;
+
+
+// XXX: Since Paul is including ipp headers perhaps we can replace uses of strkPt with psPlane
+typedef struct {
+    double  x;
+    double  y;
+} strkPt;
+
+extern bool skyToCell(strkPt *, strkAstrom *astrom, double ra, double dec);
+extern bool cellToSky(strkPt *, strkAstrom *astrom, double x, double y);
+extern bool linearizeTransforms(strkAstrom *astrom);
+
+extern bool SkyToLocal(strkPt *outPt, strkAstrom *astrom, double ra, double dec);
+extern bool LocalToSky(strkPt *outPt, strkAstrom *astrom, strkPt *inPt);
+extern bool componentBounds(int *minX, int *minY, int *maxX, int *maxY, strkAstrom *astrom, int numCols, int numRows);
+
+#endif // STREAKS_ASTROM_H
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/streakscompare.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/streakscompare.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/streakscompare.c	(revision 41117)
@@ -0,0 +1,181 @@
+#include "streaksremove.h"
+
+char *streaksProgram = "streakscompare";
+
+static pmConfig * parseArguments(int, char **);
+
+int main(int argc, char *argv[])
+{
+    bool status;
+
+    psLibInit(NULL);
+
+    pmConfig *config = parseArguments(argc, argv);
+    if (!config) {
+        streaksExit("failed to parse arguments", PS_EXIT_CONFIG_ERROR);
+    }
+
+    ippStage stage = psMetadataLookupS32(&status, config->arguments, "STAGE");
+
+    sFile *file1 = sFileOpen(config, stage, "INPUT1", NULL, true, false);
+    sFile *file2 = sFileOpen(config, stage, "INPUT2", NULL, true, false);
+
+    int ncomponents;
+    if (stage == IPP_STAGE_RAW) {
+        if (file1->nHDU != file2->nHDU) {
+            streaksExit("mages do not have same number of components\n", PS_EXIT_DATA_ERROR);
+        }
+        if (!(psFitsMoveExtNum(file1->fits, 1, true) && psFitsMoveExtNum(file2->fits, 1, true))) {
+            streaksExit("failed to skip primary header of raw file\n", PS_EXIT_DATA_ERROR);
+        }
+        ncomponents = file1->nHDU - 1;
+    } else {
+        ncomponents = 1;
+    }
+
+    int numErrors = 0;
+    for (int component = 0; component < ncomponents; component++) {
+        if (component && !(psFitsMoveExtNum(file1->fits, 1, true)
+                           && psFitsMoveExtNum(file2->fits, 1, true))) {
+            streaksExit("failed to advance to next extesion\n", PS_EXIT_DATA_ERROR);
+        }
+        readImage(file1, 0, stage, false);
+        readImage(file2, 0, stage, false);
+
+        psImage *image1 = file1->image;
+        psImage *image2 = file2->image;
+
+        // TODO: do more sanity checking. For example check that extname's  (if any) match
+        // check for matching image cubes
+        if (image1 && image2) {
+            if ((image1->numRows != image2->numRows) || (image1->numCols != image2->numCols)) {
+                streaksExit("image sizes do not match\n", PS_EXIT_DATA_ERROR);
+            }
+            int width = image1->numCols;
+            int height = image1->numRows;
+            for (int y = 0; y < height; y++) {
+                for (int x = 0; x < width; x++) {
+                    bool error = false;
+
+                    double value1 = psImageGet(image1, x, y);
+                    double value2 = psImageGet(image1, x, y);
+
+                    if (!isnan(value1) && !isnan(value2)) {
+                        if (value1 != value2) {
+                            error = true;
+                        }
+                    } else {
+                        // if one's a nan they had both better be
+                        error = ! isnan(value1) && isnan(value2);
+                    }
+
+                    if (error) {
+                        numErrors++;
+                        fprintf(stderr, "pixel at %5d %5d do not match %f %f\n", x, y, value1, value2);
+                    }
+                }
+            }
+        } else if (! ((image1 == NULL) && (image2 == NULL))) {
+            // if one image is null (video extension) both should be
+            streaksExit("image1 null but not image2!", PS_EXIT_DATA_ERROR);
+        }
+    }
+
+    fprintf(stderr, "%d errors\n", numErrors);
+
+    closeImage(file1);
+    closeImage(file2);
+
+    psFree(config);
+    psLibFinalize();
+
+    fprintf(stderr, "Found %d leaks at %s\n", psMemCheckLeaks (0, NULL, NULL, false), "streakscompare");
+
+    //  PAU
+    return 0;
+}
+static void usage(void)
+{
+    fprintf(stderr, "USAGE: streakscompare file1 file2\n");
+
+    exit(2);
+}
+
+static pmConfig *parseArguments(int argc, char **argv)
+{
+    int argnum;
+
+    pmConfig *config = pmConfigRead(&argc, argv, NULL);
+    if (!config) {
+        return NULL;
+    }
+
+    ippStage stage = IPP_STAGE_NONE;
+    if ((argnum = psArgumentGet(argc, argv, "-stage"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        stage = parseStage(argv[argnum]);
+        psMetadataAddS32(config->arguments, PS_LIST_TAIL, "STAGE", 0,
+                "pipeline stage for streak removal", stage);
+        psArgumentRemove(argnum, &argc, argv);
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "-stage is required\n");
+        usage();
+    }
+
+    // If we are asked to replace inputs with the outputs and input image is in nebulous
+    // we require mask, weight, and tmproot to all be
+    bool nebulousImage = false;
+    if ((argnum = psArgumentGet(argc, argv, "-image1"))) {
+        // for raw and chip level images we use psFits for I/O and ...
+        nebulousImage = IN_NEBULOUS(argv[argnum+1]);
+        if (CHIP_LEVEL_INPUT(stage)) {
+            psArgumentRemove(argnum, &argc, argv);
+            psMetadataAddStr(config->arguments, PS_LIST_TAIL, "INPUT1", 0,
+                    "name of input image", argv[argnum]);
+            psArgumentRemove(argnum, &argc, argv);
+        } else  {
+            // ... for warped images we use pmFPAfiles
+            if (!pmConfigFileSetsMD(config->arguments, &argc, argv, "INPUT1", "-image1", NULL)) { ;
+                psError(PS_ERR_UNKNOWN, false, "failed to process -image");
+                usage();
+            }
+        }
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "-image1 is required\n");
+        usage();
+    }
+    if ((argnum = psArgumentGet(argc, argv, "-image2"))) {
+        // for raw and chip level images we use psFits for I/O and ...
+        nebulousImage = IN_NEBULOUS(argv[argnum+1]);
+        if (CHIP_LEVEL_INPUT(stage)) {
+            psArgumentRemove(argnum, &argc, argv);
+            psMetadataAddStr(config->arguments, PS_LIST_TAIL, "INPUT2", 0,
+                    "name of input image", argv[argnum]);
+            psArgumentRemove(argnum, &argc, argv);
+        } else  {
+            // ... for warped images we use pmFPAfiles
+            if (!pmConfigFileSetsMD(config->arguments, &argc, argv, "INPUT2", "-image2", NULL)) { ;
+                psError(PS_ERR_UNKNOWN, false, "failed to process -image");
+                usage();
+            }
+        }
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "-image2 is required\n");
+        usage();
+    }
+
+    if (nebulousImage) { /* do something? */ }
+
+    if (argc != 1) {
+        psString unexpectedArguments = NULL;
+        for (int i=1; i<argc; i++) {
+            psStringAppend(&unexpectedArguments, "%s ", argv[i]);
+        }
+        psError(PS_ERR_UNKNOWN, true, "unexpected arguments: %s", unexpectedArguments);
+        psFree(unexpectedArguments);
+        usage();
+    }
+
+    return config;
+}
+
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksextern.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksextern.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksextern.c	(revision 41117)
@@ -0,0 +1,137 @@
+/* Standard includes */
+
+#include <stdio.h>
+
+/* psLib includes */
+
+#include "psMemory.h"
+
+/* streakremove includes */
+
+#include "streaksextern.h"
+#include "Line.h"
+
+/** Create a list of pixel positions from a Streaks pointer list
+    converting the equatorial coordinates into the pixel coordinates
+    using strkAstrom and building streak pixels using the specified
+    width and store in StreakPixels object returned
+
+    @param[in] streaks List of streak pointer objects to convert to
+                       pixel positions output in StreakPixels
+    @param[in] astrom Astrometry object for converting streak equatorial
+                      coordinates into pixel positions
+    @param[in] numCols number of columns in the image to map streak equatorial
+                       coordinates into pixel positions
+    @param[in] numRows number of rows in the image to map streak equatorial
+                       coordinates into pixel positions
+    @return list of PixelPos pointers for the streaks mapped to the image
+            pixels
+*/
+    
+StreakPixels *streak_on_component(Streaks *streaks, strkAstrom *astrom,
+                                  int numCols, int numRows)
+{
+    int i;
+    Line line;
+    StreakPixels *pixels = psImageAlloc(numCols, numRows, PS_TYPE_U8);
+    psImageInit(pixels, 0);
+    int streaksOnComponent = 0;
+
+    int minX, minY, maxX, maxY;
+
+    // find the chip dimensions in the tangent-plane coordinates (length of hypotenuse)
+    componentBounds (&minX, &minY, &maxX, &maxY, astrom, numCols, numRows);
+
+    for (i = 0; i != streaks->size; ++i)
+    {
+        /* Convert streak equatorial coordinates into pixel based line */
+
+        line.width = streaks->list[i].width;
+
+	/* Use tangent plane coordinates to narrow down the ra,dec range of the line closer to
+	 * the chip boundaries.  Use these new ra,dec positions to generate the line on the
+	 * chip using the full non-linear astrometry */
+	   
+	// project the ends of the line using a linear projection centered on the chip center:
+	Line full;
+	SkyToLocal (&full.begin, astrom, streaks->list[i].ra1, streaks->list[i].dec1);
+	SkyToLocal (&full.end,   astrom, streaks->list[i].ra2, streaks->list[i].dec2);
+
+	// clip the line to a square box with diameter = hypotenuse of the chip image centerd
+	// on the chip center in tangent-plane coordinates.  skip the rest of this streak if
+	// the line does not intersect this region
+	if (!LineClipFull (&full, minX, minY, maxX, maxY)) {
+	    continue;
+	}
+
+	// convert the end points back into ra, dec pairs:
+	strkPt sky1, sky2;
+	LocalToSky (&sky1, astrom, &full.begin);
+	LocalToSky (&sky2, astrom, &full.end);
+
+        if (skyToCell (&line.begin, astrom, sky1.x, sky1.y) &&
+            skyToCell (&line.end,   astrom, sky2.x, sky2.y) &&
+            LineClip (&line, numCols, numRows))
+        {
+            PixelsFromLine (pixels, &line, numCols, numRows);
+            streaksOnComponent++;
+        }
+    }
+    fprintf(stderr, "%d streaks on this component\n", streaksOnComponent);
+    return pixels;
+}
+
+static void streaksFree(Streaks *streaks);
+
+/** Read a list of streak equatorial coordinate definitions and width
+    from a text file.  The format is:
+
+    @verbatim
+    numStreaks
+    ra1 dec1 ra2 dec2 width
+    ...
+    @endverbatim
+
+    @param[in] fileName path to streak text file to read
+    @return a list of streak objects.  If there is an error in reading
+            the text file, return NULL
+*/
+
+Streaks *readStreaksFile(char *fileName)
+{
+    int i;
+    Streaks *streaks = NULL;
+    FILE *file = fopen (fileName, "r");
+    if (file != NULL)
+    {
+        streaks = psAlloc (sizeof(Streaks));
+        if (fscanf (file, "%u", &streaks->size) != 1)
+        {
+            psFree (streaks);
+            fclose (file);
+            return NULL;
+        }
+        streaks->list = psAlloc (streaks->size * sizeof(streak));
+        psMemSetDeallocator(streaks, (psFreeFunc) streaksFree);
+        for (i = 0; i != streaks->size; ++i)
+        {
+            if (fscanf (file, "%lf %lf %lf %lf %lf",
+                        &streaks->list[i].ra1, &streaks->list[i].dec1,
+                        &streaks->list[i].ra2, &streaks->list[i].dec2,
+                        &streaks->list[i].width) != 5)
+            {
+                psFree (streaks->list);
+                psFree (streaks);
+                streaks = NULL;
+                break;
+            }
+        }
+        fclose (file);
+    }
+    return streaks;
+}
+
+static void streaksFree(Streaks *streaks)
+{
+    psFree(streaks->list);
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksextern.h
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksextern.h	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksextern.h	(revision 41117)
@@ -0,0 +1,75 @@
+#ifndef STREAKSEXTERN_H
+#define STREAKSEXTERN_H
+
+/** psLib includes */
+
+#include "psImage.h"
+
+/** streakremove includes */
+
+#include "streaksastrom.h"
+
+/** Equatorial coordinates of streak endpoints in radians */
+
+typedef struct {
+    double ra1;
+    double dec1;
+    double ra2;
+    double dec2;
+    double width;   /**< Width of streak in pixels */
+} streak;
+
+/** List of streak objects to map to image pixel positions */
+
+typedef struct {
+    unsigned int size;
+    streak *list;
+} Streaks;
+
+/** Pixel position within an image */
+
+typedef struct {
+    unsigned int x; /**< X (column) pixel position */
+    unsigned int y; /**< Y (row) pixel position */
+} PixelPos;
+
+/** For now, use psArray to define a list of PixelPos pointers */
+
+typedef psImage StreakPixels;
+
+/** Create a list of pixel positions from a Streaks pointer list
+    converting the equatorial coordinates into the pixel coordinates
+    using strkAstrom and building streak pixels using the specified
+    width and store in StreakPixels object returned
+
+    @param[in] streaks List of streak pointer objects to convert to
+                       pixel positions output in StreakPixels
+    @param[in] astrom Astrometry object for converting streak equatorial
+                      coordinates into pixel positions
+    @param[in] numCols number of columns in the image to map streak equatorial
+                       coordinates into pixel positions
+    @param[in] numRows number of rows in the image to map streak equatorial
+                       coordinates into pixel positions
+    @return list of PixelPos pointers for the streaks mapped to the image
+            pixels
+*/
+
+extern StreakPixels *streak_on_component(Streaks *streaks, strkAstrom *astrom,
+                                         int numCols, int numRows);
+
+/** Read a list of streak equatorial coordinate definitions and width
+    from a text file.  The format is:
+
+    @verbatim
+    numStreaks
+    ra1 dec1 ra2 dec2 width
+    @endverbatim
+
+    @param[in] fileName path to streak text file to read
+    @return a list of streak objects.  If there is an error in reading
+            the text file, return NULL
+*/
+
+extern Streaks *readStreaksFile(char *fileName);
+
+#endif /* STREAKSEXTERN_H */
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksio.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksio.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksio.c	(revision 41117)
@@ -0,0 +1,1338 @@
+#include "streaksremove.h"
+#include "libgen.h"
+#include "unistd.h"
+
+static void sFileFree(sFile *sfile);
+static void streakFilesFree(streakFiles *sf);
+static void freeImages(streakFiles *);
+static void setExciseValue(sFile *in);
+
+static nebServer *ourNebServer = NULL;
+
+// Assumptions about the file structure of non-raw files
+// The 'image' for each file (image, mask weight) is contained in the first
+// extension.
+
+
+// open the files required for streaks procesing.
+// if remove is true the calling program is streaksremove and the recovery files are outputs
+// if false the recovery files are inputs
+streakFiles *openFiles(pmConfig *config, bool remove, char *program_name)
+{
+    bool status;
+    streakFiles *sf = psAlloc(sizeof(streakFiles));
+    psMemSetDeallocator(sf, (psFreeFunc) streakFilesFree);
+    memset(sf, 0, sizeof(*sf));
+
+    sf->config = config;
+    sf->program_name = basename(program_name);
+
+    if (remove) {
+        // remember pointer so that streaksExit can delete temps
+        setStreakFiles(sf);
+    }
+
+
+    // error checking is done by sFileOpen. If a file can't be opened we just exit
+    ippStage stage = psMetadataLookupS32(&status, config->arguments, "STAGE");
+
+    sf->stage = stage;
+    sf->extnum = 0;
+
+    sf->class_id = psMetadataLookupStr(&status, config->arguments, "CLASS_ID");
+
+    sf->inImage = sFileOpen(config, stage, "INPUT", NULL, true, false);
+    sf->nHDU = sf->inImage->nHDU;
+
+    // don't need to free inputBasename see basename(3)
+    // The names of the temporary and recovery files are taken from the input
+    char *inputBasename = basename(sf->inImage->name);
+    sf->outImage = sFileOpen(config, stage, "OUTPUT", inputBasename, true, true);
+
+    if (remove) {
+        // XXX: the recovery file should be required if stage is raw and the replace flag is set
+        // that is if the input raw image is to be destroyed
+        sf->recImage = sFileOpen(config, stage, "RECOVERY", inputBasename, false, false);
+    } else {
+        sf->recImage = sFileOpen(config, stage, "RECOVERY.IMAGE", NULL, true, false);
+    }
+
+    sf->inMask = sFileOpen(config, stage, "INPUT.MASK", NULL, false, false);
+    if (sf->inMask && (sf->stage != IPP_STAGE_RAW)) {
+        inputBasename = basename(sf->inMask->name);
+        sf->outMask = sFileOpen(config, stage, "OUTPUT", inputBasename, true, true);
+        if (remove) {
+            sf->recMask = sFileOpen(config, stage, "RECOVERY", inputBasename, false, false);
+        } else {
+            sf->recMask = sFileOpen(config, stage, "RECOVERY.MASK", NULL, true, false);
+        }
+    }
+    // If we're in streaksremove and operating on a chip stage image
+    // if we are passed a mask it is camera stage mask and we also
+    // need to destreak the chip level mask file as well
+    // If it doesn't exist, we didn't have a camera mask
+    if (remove && sf->inMask && (stage == IPP_STAGE_CHIP)) {
+        sf->inChMask = sFileOpen(config, stage, "INPUT.CHMASK", NULL, false, false);
+        if (sf->inChMask) {
+            inputBasename = basename(sf->inChMask->name);
+            sf->outChMask = sFileOpen(config, stage, "OUTPUT", inputBasename, true, true);
+            sf->recChMask = sFileOpen(config, stage, "RECOVERY", inputBasename, false, false);
+        }
+    }
+
+    sf->inWeight = sFileOpen(config, stage, "INPUT.WEIGHT", NULL, false, false);
+    if (sf->inWeight) {
+        inputBasename = basename(sf->inWeight->name);
+        sf->outWeight = sFileOpen(config, stage, "OUTPUT", inputBasename, true, true);
+        if (remove) {
+            sf->recWeight = sFileOpen(config, stage, "RECOVERY", inputBasename, false, false);
+        } else {
+            sf->recWeight = sFileOpen(config, stage, "RECOVERY.WEIGHT", NULL, true, false);
+        }
+    }
+    if (remove) {
+        sf->inSources = sFileOpen(config, stage, "SOURCES", NULL, false, false);
+        if (sf->inSources) {
+            inputBasename = basename(sf->inSources->name);
+            sf->outSources = sFileOpen(config, stage, "OUTPUT", inputBasename, true, true);
+        }
+    }
+
+    psElemType tileType;                // Type corresponding to "long"
+    if (sizeof(long) == sizeof(psS64)) {
+        tileType = PS_TYPE_S64;
+    } else if (sizeof(long) == sizeof(psS32)) {
+        tileType = PS_TYPE_S32;
+    } else {
+        psAbort("can't map (long) type to a psLib type");
+    }
+
+    sf->tiles = psVectorAlloc(3, tileType); // Tile sizes
+    if (tileType == PS_TYPE_S64) {
+        sf->tiles->data.S64[0] = 0;
+        sf->tiles->data.S64[1] = 1;
+        sf->tiles->data.S64[2] = 1;
+    } else {
+        sf->tiles->data.S32[0] = 0;
+        sf->tiles->data.S32[1] = 1;
+        sf->tiles->data.S32[2] = 1;
+    }
+
+    sf->transparentStreaks = psMetadataLookupF64(&status, config->arguments, "TRANSPARENT_STREAKS");
+
+    return sf;
+}
+
+static void
+streakFilesFree(streakFiles *sf)
+{
+    freeImages(sf);
+    psFree(sf->stats);
+    psFree(sf->diffedPixels);
+    psFree(sf->tiles);
+    psFree(sf->view);
+    psFree(sf->astrom);
+}
+
+
+// getNebServer()
+//
+// it gets created the first time a nebulous file is accessed.
+// if config is passed in we are to create it and exit if not able.
+// if config is null we return NULL indicating that there are no nebulous files in use
+// The purpose of this is to avoid passing extra arguments between all of the functions.
+// but it is probably a bad idea.
+//
+static nebServer *getNebServer(pmConfig *config)
+{
+    if (ourNebServer) {
+        return ourNebServer;
+    }
+
+    if (!config) {
+        return NULL;
+    }
+
+    psString serverURI = getenv("NEB_SERVER");
+
+    // XXX: Note: all of the flags to psError should be true, but I don't think nebclient
+    // uses psError
+    if (!serverURI) {
+        bool status;
+        serverURI = psMetadataLookupStr(&status, config->site, "NEB_SERVER");
+        if (!status) {
+            psError(PM_ERR_CONFIG, true, "failed to lookup config value for NEB_SERVER.");
+            streaksExit("", PS_EXIT_CONFIG_ERROR);
+        }
+    }
+
+    if (!serverURI) {
+        psError(PM_ERR_CONFIG, true, "Could not determine nebulous server URI.");
+
+        streaksExit("", PS_EXIT_CONFIG_ERROR);
+    }
+
+    ourNebServer = nebServerAlloc(serverURI);
+    if (!ourNebServer) {
+        psError(PM_ERR_SYS, true, "failed to create a nebServer object from %s.", serverURI);
+        streaksExit("", PS_EXIT_CONFIG_ERROR);
+    }
+
+    return ourNebServer;
+}
+
+// figure out if a nebulous instance is a non-destreaked file
+static bool
+nebFileIsDestreaked(sFile *sfile)
+{
+    if (!sfile->resolved_name) {
+        psError(PS_ERR_PROGRAMMING, true, "resolved name is null");
+        return false;
+    }
+    psFits *fits = psFitsOpen(sfile->resolved_name, "r");
+    if (!fits) {
+        psError(PS_ERR_IO, true, "failed open %s", sfile->name);
+        // can't tell if it is a destreaked file
+        return false;
+    }
+    psMetadata *header = psFitsReadHeader(NULL, fits);
+    if (!header) {
+        psError(PS_ERR_IO, true, "failed to read header for: %s", sfile->name);
+        return false;
+    }
+    bool mdok;
+    bool isDestreaked = psMetadataLookupBool(&mdok, header, "PSDESTRK");
+    if (mdok && isDestreaked) {
+        return true;
+    } else {
+        psError(PS_ERR_IO, false, "output file already exists and may not be de-streaked: %s", sfile->name);
+        return false;
+    }
+}
+
+static psString
+resolveFilename(pmConfig *config, sFile *sfile, bool create, bool checkIfDestreaked)
+{
+    sfile->inNebulous = IN_NEBULOUS(sfile->name);
+
+    if (sfile->inNebulous) {
+        // make sure we have our neb server connection
+        nebServer *server = getNebServer(config);
+        if (create) {
+            // delete the existing file, since there may be more than one
+            // instance. It will get created below in pmConfigConvertFilename
+            if ((sfile->resolved_name = nebFind(server, sfile->name)) != NULL) {
+                if (checkIfDestreaked && !nebFileIsDestreaked(sfile)) {
+                    psError(PS_ERR_IO, false, "attempting to delete file that has not been destreaked %s", sfile->name);
+                    return NULL;
+                }
+                nebFree(sfile->resolved_name);
+                sfile->resolved_name = NULL;
+                nebDelete(server, sfile->name);
+            }
+        }
+    }
+
+    return pmConfigConvertFilename(sfile->name, config, create, create);
+}
+
+sFile *sFileOpen(pmConfig *config, ippStage stage, psString fileSelect,
+    psString outputFilename, bool required, bool checkIfDestreaked)
+{
+    bool status;
+    sFile *sfile = psAlloc(sizeof(sFile));
+    memset(sfile, 0, sizeof(sFile));
+
+    // We use psFits directly to read the image unless the stage is warp
+    // or diff. In those cases we use the pmFPAfile functions to read the image file
+    // to make managing the astrometry easy.
+    // The reason we don't use pmFPAfile in all cases is that I was having trouble getting
+    // all of the keywords in the raw image files written to the output destreaked files
+
+    if (!outputFilename && !CHIP_LEVEL_INPUT(stage) && !strcmp(fileSelect, "INPUT")) {
+        // stage is warp or diff AND fileSelect eq "INPUT"
+        // get data from pmFPAfile.
+
+        // we need to know what the nebulous and real filenames are so we steal
+        // some code from pmFPAfileDefineFromArgs
+        // XXX: create a pmFPAfile function that does this and use it there
+        psArray *infiles = psMetadataLookupPtr(&status, config->arguments, "INPUT");
+        if (!status) {
+            psError(PS_ERR_PROGRAMMING, false, "INPUT not found in config->arguments");
+            streaksExit("", PS_EXIT_PROG_ERROR);
+        }
+        if (infiles->n != 1) {
+            psError(PS_ERR_IO, false, "Found n == %ld files in %s in arguments expencted 1\n",
+                infiles->n, "INPUT");
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        // end of file name lookup code adapted from pmFPAfileDefineFromArgs
+        //
+        sfile->name = psStringCopy(infiles->data[0]);
+        sfile->inNebulous = IN_NEBULOUS(sfile->name);
+
+        // XXX: I should probably be using a different file rule for diffs, but I don't
+        // have an input rule that takes a diff image.
+        // Since they're skycells, this should be compatible
+        sfile->pmfile = pmFPAfileDefineFromArgs(&status, config, "PPSUB.INPUT", "INPUT");
+        if (!sfile->pmfile) {
+            psError(PS_ERR_IO, false, "Failed to define file for %s", fileSelect);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+
+        pmFPAview *view = pmFPAviewAlloc(0);
+        if (!pmFPAfileIOChecks(config, view, PM_FPA_BEFORE)) {
+            psError(PS_ERR_UNKNOWN, false, "Failed to load input.");
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        psFree(view);
+        sfile->resolved_name = psStringCopy(sfile->pmfile->filename);
+
+        sfile->nHDU = psFitsGetSize(sfile->pmfile->fits);
+
+        // copy header from fpu
+        sfile->header = (psMetadata*) psMemIncrRefCounter(sfile->pmfile->fpa->hdu->header);
+
+        return sfile;
+    }
+
+    // For all other files we use using psFits for i/o
+
+    psString name = psMetadataLookupStr(&status, config->arguments, fileSelect);
+    if (!status || !name) {
+        sFileFree(sfile);
+        if (required) {
+            psError(PS_ERR_IO, false, "Failed to lookup name for %s", fileSelect);
+            streaksExit("", 1);
+        }
+        return NULL;
+    }
+
+    // if outputFilename is not null name it contains the "directory" (perhaps with a prefix like SR_)
+    // and outputFilename is the basename name of the file (or nebulous key)
+    // and the file is to be opened for writing
+    if (outputFilename) {
+        sfile->write = true;
+        psStringAppend(&sfile->name, "%s%s", name, outputFilename);
+        sfile->resolved_name = resolveFilename(config, sfile, true, checkIfDestreaked);
+        if (!sfile->resolved_name) {
+            psError(PS_ERR_IO, false, "Failed to resolve filename for %s", sfile->name);
+            sFileFree(sfile);
+            streaksExit("", 1);
+        }
+        sfile->fits = psFitsOpen(sfile->resolved_name, "w");
+        if (sfile->fits) {
+            sfile->fits->options = psFitsOptionsAlloc();
+        }
+    } else {
+        sfile->name = psStringCopy(name);
+        sfile->resolved_name = resolveFilename(config, sfile, false, false);
+        if (!sfile->resolved_name) {
+            psError(PS_ERR_IO, false, "Failed to resolve name for %s", sfile->name);
+            sFileFree(sfile);
+            streaksExit("", 1);
+        }
+        sfile->fits = psFitsOpen(sfile->resolved_name, "r");
+        if (sfile->fits) {
+            sfile->nHDU = psFitsGetSize(sfile->fits);
+        }
+    }
+
+    if (!sfile->fits) {
+        psError(PS_ERR_IO, false, "failed to open fits file %s for %s",
+                    sfile->resolved_name, outputFilename ? "writing" : "reading");
+        sFileFree(sfile);
+        streaksExit("", 1);
+    }
+
+    return sfile;
+}
+
+
+static bool
+moveExt(sFile *sfile, int extnum)
+{
+    bool status = psFitsMoveExtNum(sfile->fits, extnum, false);
+    if (!status) {
+        psError(PS_ERR_IO, false,
+            "failed to move to extension %d for %s", extnum, sfile->resolved_name);
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+    return true;
+}
+
+bool
+streakFilesNextExtension(streakFiles *sf)
+{
+    // unless stage is raw when we get here we're done
+    if (sf->stage != IPP_STAGE_RAW) {
+        if (sf->view) {
+            sf->view->readout = -1;
+            pmFPAfileIOChecks(sf->config, sf->view, PM_FPA_AFTER);
+            sf->view->cell = -1;
+            pmFPAfileIOChecks(sf->config, sf->view, PM_FPA_AFTER);
+            sf->view->chip = -1;
+            pmFPAfileIOChecks(sf->config, sf->view, PM_FPA_AFTER);
+        }
+        return false;
+    }
+
+    ++sf->extnum;
+    if (sf->nHDU == 1) {
+        // return true the first time through
+        return (sf->extnum == 1);
+    }
+    if (sf->extnum < sf->nHDU) {
+        moveExt(sf->inImage, sf->extnum);
+        // No mask and weight images for raw stage
+        return true;
+    } else {
+        // we're all done
+        return false;
+    }
+}
+
+void
+addDestreakKeyword(psMetadata *header, bool value)
+{
+    psMetadataAddBool(header, PS_LIST_TAIL, "PSDESTRK", PS_META_REPLACE,
+        "Have streaks been removed from image?", value);
+}
+
+void
+addRecoveryKeyword(psMetadata *header)
+{
+    psMetadataAddBool(header, PS_LIST_TAIL, "PSRECOVR", PS_META_REPLACE,
+        "Does this image contain excised streak pixels?", true);
+}
+
+void
+copyPHU(streakFiles *sfiles, bool remove, bool destreak)
+{
+    psAssert(sfiles->stage == IPP_STAGE_RAW, "copyPHU should only be used for raw stage");
+
+    psMetadata *imageHeader = psFitsReadHeader(NULL, sfiles->inImage->fits);
+    if (!imageHeader) {
+        psError(PS_ERR_IO, false, "failed to read primary header from %s\n",
+            sfiles->inImage->resolved_name);
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+    psMetadata *recHeader = NULL;
+    if (remove && sfiles->recImage) {
+       recHeader = psMetadataCopy(NULL, imageHeader);
+       addRecoveryKeyword(recHeader);
+    }
+
+    // add keyword indicating that streaks have been removed
+    addDestreakKeyword(imageHeader, destreak);
+
+    if (!psFitsWriteBlank(sfiles->outImage->fits, imageHeader, NULL)) {
+        psError(PS_ERR_IO, false, "failed to write primary header to %s",
+            sfiles->outImage->resolved_name);
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+    if (recHeader && !psFitsWriteBlank(sfiles->recImage->fits, recHeader, NULL)) {
+        psError(PS_ERR_IO, false, "failed to write primary header to %s",
+            sfiles->recImage->resolved_name);
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+    psFree(recHeader);
+    recHeader = NULL;
+    psFree(imageHeader);
+
+    sFile *inMask = sfiles->inMask;
+    if (inMask && sfiles->outMask) {
+        psMetadata *maskHeader = psFitsReadHeader(NULL, inMask->fits);
+        if (!maskHeader) {
+            psError(PS_ERR_IO, false, "failed to read primary header from %s\n",
+                sfiles->inMask->resolved_name);
+            streaksExit("", 1);
+        }
+        if (remove && sfiles->recMask) {
+            recHeader = psMetadataCopy(NULL, maskHeader);
+            // add keyword indicating that this is the recovery image
+           addRecoveryKeyword(recHeader);
+        }
+        // add keyword indicating that streaks have been removed
+        addDestreakKeyword(maskHeader, destreak);
+        if (!psFitsWriteBlank(sfiles->outMask->fits, maskHeader, NULL)) {
+            psError(PS_ERR_IO, false, "failed to write primary header to %s",
+                sfiles->outMask->resolved_name);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        if (recHeader && !psFitsWriteBlank(sfiles->recMask->fits, recHeader, NULL)) {
+            psError(PS_ERR_IO, false, "failed to write primary header to %s",
+                sfiles->recMask->resolved_name);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        psFree(recHeader);
+        recHeader = NULL;
+        psFree(maskHeader);
+    }
+    sFile *inWeight = sfiles->inWeight;
+    if (inWeight) {
+        psMetadata *weightHeader = psFitsReadHeader(NULL, inWeight->fits);
+        if (!weightHeader) {
+            psError(PS_ERR_IO, false, "failed to read primary header from %s\n",
+                sfiles->inWeight->resolved_name);
+            streaksExit("", 1);
+        }
+        if (remove && sfiles->recWeight) {
+            recHeader = psMetadataCopy(NULL, weightHeader);
+            // add keyword indicating that this is a recovery image
+           addRecoveryKeyword(recHeader);
+        }
+
+        // add keyword indicating that streaks have been removed
+        addDestreakKeyword(weightHeader, destreak);
+        if (!psFitsWriteBlank(sfiles->outWeight->fits, weightHeader, NULL)) {
+            psError(PS_ERR_IO, false, "failed to write primary header to %s",
+                sfiles->outWeight->resolved_name);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        if (recHeader && !psFitsWriteBlank(sfiles->recWeight->fits, recHeader, NULL)) {
+            psError(PS_ERR_IO, false, "failed to write primary header to %s",
+                sfiles->recWeight->resolved_name);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        psFree(weightHeader);
+        psFree(recHeader);
+    }
+}
+
+// Determine whether the current header for this file is an image or not
+// Find a cleaner way to do this
+static bool
+isImage(sFile *in)
+{
+    bool status;
+    psString exttype = psMetadataLookupStr(&status, in->header, "EXTTYPE");
+    if (exttype && !strcmp(exttype, "IMAGE")) {
+        return true;
+    }
+    // examine current header and determine if it is an image
+    psString xtension = psMetadataLookupStr(&status, in->header, "XTENSION");
+    if (xtension) {
+        if (!strcmp(xtension, "IMAGE")) {
+            return true;
+        } else if (!strcmp(xtension, "BINTABLE")) {
+            psString ztension = psMetadataLookupStr(&status, in->header, "ZTENSION");
+            if (ztension && !strcmp(ztension, "IMAGE")) {
+                return true;
+            }
+        }
+    } else if (psMetadataLookupBool(&status, in->header, "ZIMAGE")) {
+        return true;
+    } else if (in->nHDU == 1) {
+        // no extensions in the file, can just return true? For now require
+        // at least one dimension
+        int naxis =  psMetadataLookupS32(&status, in->header, "NAXIS");
+        return naxis > 0;
+    }
+    return false;
+}
+
+void
+readImageFrom_pmFile(streakFiles *sf)
+{
+    // XXX: This function assumes that it is only used for a single
+    // chip single cell FPA (i.e. a skycell)
+    assert(sf->inImage->pmfile->fpa->chips->n == 1);
+
+    pmFPAview *view = sf->view;
+    if (view) {
+        // The view was allocated and the chip set by setupAstrometry()
+        assert(view->chip == 0);
+    } else {
+        sf->view = pmFPAviewAlloc(0);
+        view = sf->view;
+        sf->chip = pmFPAviewNextChip(view, sf->inImage->pmfile->fpa, 1);
+        assert(sf->chip != NULL);
+    }
+    view->cell = 0;
+    sf->cell =  pmFPAviewThisCell(view, sf->inImage->pmfile->fpa);
+
+    if (!sf->cell) {
+        streaksExit("no cells found in chip", PS_EXIT_PROG_ERROR);
+    }
+    if (sf->cell->readouts->n != 1) {
+        psError(PS_ERR_PROGRAMMING, true, "unexpected number of readouts found: %ld", sf->cell->readouts->n);
+        streaksExit("", PS_EXIT_PROG_ERROR);
+    }
+    view->readout = 0;
+    pmReadout *readout = pmFPAviewThisReadout(view, sf->inImage->pmfile->fpa);
+    if (!readout) {
+        streaksExit("readout 0 not found", PS_EXIT_PROG_ERROR);
+    }
+
+    sf->inImage->image = (psImage*) psMemIncrRefCounter(readout->image);
+    sf->inImage->numCols = readout->image->numCols;
+    sf->inImage->numRows = readout->image->numRows;
+    setExciseValue(sf->inImage);
+}
+
+static void
+setDataExtent(ippStage stage, sFile *in, bool rawImage)
+{
+    if (rawImage) {
+        psString datasec = psMetadataLookupStr(NULL, in->header, "DATASEC");
+        if (!datasec) {
+            psError(PS_ERR_IO, false, "failed to find DATASEC in header");
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        int xmin, xmax, ymin, ymax;
+        sscanf(datasec, "[%d:%d,%d:%d]", &xmin, &xmax, &ymin, &ymax);
+
+        // I've inadvertantly introduced an implicit assumption that cell coordinates
+        // are the same as image coordinates. This is true for GPC1
+        // Test for this condition not being true
+        if (xmin != 1) {
+            psError(PS_ERR_IO, true, "can't handle datasec with minimum x != 1: %s", datasec);
+            streaksExit("", PS_EXIT_PROG_ERROR);
+        }
+        if (ymin != 1) {
+            psError(PS_ERR_IO, true, "can't handle datasec with minimum y != 1: %s", datasec);
+            streaksExit("", PS_EXIT_PROG_ERROR);
+        }
+
+        // These values are different than the size of the actual image which includes
+        // the bias area.
+        in->numCols = xmax - xmin + 1;
+        in->numRows = ymax - ymin + 1;
+    } else {
+        in->numCols = in->image->numCols;
+        in->numRows = in->image->numRows;
+    }
+}
+
+void
+readImage(sFile *in, int extnum, ippStage stage, bool isMask)
+{
+    psRegion region = {0, 0, 0, 0};
+
+    if (in->header) psFree(in->header);
+    if (in->image) {
+        psFree(in->image);
+        in->image = NULL;
+    }
+    if (in->imagecube) {
+        psFree(in->imagecube);
+        in->imagecube = NULL;
+    }
+
+    in->header = psFitsReadHeader(NULL, in->fits);
+    if (!in->header) {
+        psError(PS_ERR_IO, false, "failed to read header from %s extnum: %d",
+            in->resolved_name, extnum);
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+
+    bool status;
+    in->extname = psMetadataLookupStr(&status, in->header, "EXTNAME");
+
+    if (!isImage(in)) {
+        psLogMsg("streaksremove", PS_LOG_INFO, "%s contains no image\n", in->name);
+        return;
+    }
+
+    in->numZPlanes = psMetadataLookupS32(NULL, in->header, "NAXIS3");
+
+    if (in->numZPlanes == 0) {
+        in->image = psFitsReadImage(in->fits, region, 0);
+        if (!in->image) {
+            psError(PS_ERR_IO, false, "failed to read image from %s extnum: %d",
+                in->resolved_name, extnum);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        setExciseValue(in);
+    }  else {
+        if (stage != IPP_STAGE_RAW) {
+            psError(PS_ERR_PROGRAMMING, true, "unexpected image cube found in non-raw image");
+            streaksExit("", PS_EXIT_PROG_ERROR);
+        }
+        in->imagecube = psFitsReadImageCube(in->fits, region);
+        if (!in->imagecube) {
+            psError(PS_ERR_IO, false, "failed to read image cube from %s extnum: %d",
+                in->resolved_name, extnum);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+
+        // Ensure input is of the expected type
+        psElemType expected = isMask ? PS_TYPE_IMAGE_MASK : PS_TYPE_F32; // Expected type for image
+        for (int i = 0; i < in->imagecube->n; i++) {
+            psImage *image = in->imagecube->data[i]; // Image of interest
+            if (image->type.type != expected) {
+                psImage *temp = psImageCopy(NULL, image, expected);
+                psFree(image);
+                in->imagecube->data[i] = temp;
+            }
+        }
+    }
+    setDataExtent(stage, in, (stage == IPP_STAGE_RAW) && !isMask);
+}
+
+static void
+setFitsOptions(sFile *sfile, int bitpix, float bscale, float bzero, psFitsCompressionType compType,
+    psVector *tiles)
+{
+    if (!sfile || !sfile->write) {
+        return;
+    }
+    if (sfile->fits->options) {
+        psFree(sfile->fits->options);
+    }
+    sfile->fits->options = psFitsOptionsAlloc();
+    sfile->fits->options->scaling = PS_FITS_SCALE_MANUAL;
+    sfile->fits->options->fuzz = false;
+    sfile->fits->options->bitpix = bitpix;
+    sfile->fits->options->bscale = bscale;
+    sfile->fits->options->bzero = bzero;
+
+    psFitsSetCompression(sfile->fits, compType, tiles, 8, 0, 0);
+}
+
+void
+copyFitsOptions(sFile *out, sFile *rec, sFile *in, psVector *tiles)
+{
+    bool mdok;
+    psString compTypeStr = psMetadataLookupStr(&mdok, in->header, "ZCMPTYPE");
+    psFitsCompressionType compType = psFitsCompressionTypeFromString(compTypeStr);
+    if (compType == PS_FITS_COMPRESS_NONE) {
+        return;
+    }
+    // Get current BITPIX, BSCALE, BZERO, EXTNAME
+    // Probably not necessary to look the numerical values up in this
+    // way, but guards against changes to psLib and cfitsio FITS
+    // handling.
+    psMetadataItem *bitpixItem = psMetadataLookup(in->header, "BITPIX");
+    psAssert(bitpixItem, "Every FITS image should have BITPIX");
+    int bitpix = psMetadataItemParseS32(bitpixItem);
+    psMetadataItem *bscaleItem = psMetadataLookup(in->header, "BSCALE");
+
+    float bscale;
+    if (!bscaleItem) {
+        psWarning("BSCALE isn't set; defaulting to unity");
+        bscale = 1.0;
+    } else {
+        bscale = psMetadataItemParseF32(bscaleItem);
+    }
+    psMetadataItem *bzeroItem = psMetadataLookup(in->header, "BZERO");
+    float bzero;
+    if (!bzeroItem) {
+        psWarning("BZERO isn't set; defaulting to zero");
+        bzero = 0.0;
+    } else {
+        bzero = psMetadataItemParseF32(bzeroItem);
+    }
+
+#ifdef STREAKS_COMPRESS_OUTPUT
+    // printf("%d %f %f\n", bitpix, bscale, bzero);
+    setFitsOptions(out, bitpix, bscale, bzero, compType, tiles);
+    setFitsOptions(rec, bitpix, bscale, bzero, compType, tiles);
+#endif
+}
+
+
+void
+copyTable(sFile *out, sFile *in, int extnum)
+{
+    bool mdok;
+    psString xtension = psMetadataLookupStr(&mdok, in->header, "XTENSION");
+    psString extname = psMetadataLookupStr(&mdok, in->header, "EXTNAME");
+
+    if (!xtension) {
+        psWarning("extnum %d has no image and xtension is not defined in %s",
+            extnum, in->resolved_name);
+        // XXX abort?
+        return;
+    }
+    if (!extname) {
+        psWarning("extnum %d has no image and extname not defined in %s",
+            extnum, in->resolved_name);
+        // XXX abort?
+        return;
+    }
+    psFitsTable *table = psFitsReadTableNew(in->fits);
+    if (!table) {
+        psError(PS_ERR_UNKNOWN, false, "failed to read table in extension %d from in->resolved name", extnum);
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+
+    if (!psFitsWriteTableNew(out->fits, out->header, table, extname)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to copy table in extension %d", extnum);
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+    psFree(table);
+}
+
+static void
+createNewImage(sFile *out, sFile *in, int extnum, double initValue)
+{
+    out->image = psImageRecycle(out->image, in->image->numCols, in->image->numRows, in->image->type.type);
+    if (!out->image) {
+        psError(PS_ERR_UNKNOWN, false, "failed to allocate image for extnsion %d", extnum);
+        streaksExit("", PS_EXIT_UNKNOWN_ERROR);
+    }
+    psImageInit(out->image, initValue);
+}
+
+void
+setupImageRefs(sFile *out, sFile *recoveryOut, sFile *in, int extnum, bool exciseAll)
+{
+    if (!in->image) {
+        // Input file is likely corrupt. XXX: Shouldn't we have caught this earlier?
+        psError(PS_ERR_UNKNOWN, true, "image is null for extension %d of %s file is likely corrupt", extnum, in->name);
+        streaksExit("", PS_EXIT_UNKNOWN_ERROR);
+    }
+    if (!exciseAll) {
+        // set output image to be the input image
+        out->image = (psImage*) psMemIncrRefCounter(in->image);
+        if (recoveryOut) {
+            // create a recovery image initialized to NAN
+            createNewImage(recoveryOut , in, extnum, NAN);
+        }
+    } else {
+        // Excising all pixels in the input
+        // set the output to an image all NANs
+        createNewImage(out, in, extnum, NAN);
+        if (recoveryOut) {
+            // save the whole input as the recovery image
+            recoveryOut->image = (psImage*) psMemIncrRefCounter(in->image);
+        }
+    }
+}
+
+void
+writeImage(sFile *sfile, psString extname, int extnum)
+{
+    if (!sfile || !sfile->image) {
+        return;
+    }
+    streaksVersionHeaderFull(sfile->header);
+
+    if (!psFitsWriteImage(sfile->fits, sfile->header, sfile->image, 0, extname)) {
+        psError(PS_ERR_IO, false, "failed to write image to %s extnum: %d",
+            sfile->resolved_name, extnum);
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+    psFree(sfile->image);
+    sfile->image = NULL;
+    psFree(sfile->header);
+    sfile->header = NULL;
+}
+
+void
+writeImageCube(sFile *sfile, psArray *imagecube, psString extname, int extnum)
+{
+    if (!sfile || !imagecube) {
+        return;
+    }
+    streaksVersionHeaderFull(sfile->header);
+    if (!psFitsWriteImageCube(sfile->fits, sfile->header, imagecube, extname)) {
+        psError(PS_ERR_IO, false, "failed to write image to %s extnum: %d",
+            sfile->resolved_name, extnum);
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+    psFree(sfile->header);
+    sfile->header = NULL;
+}
+
+void
+sFileFree(sFile *sfile)
+{
+    if (!sfile) {
+        return;
+    }
+    psFree(sfile->name);
+    psFree(sfile->resolved_name);
+    psFree(sfile->fits);
+    psFree(sfile);
+}
+
+void
+closeImage(sFile *sfile)
+{
+    if (!sfile) {
+        return;
+    }
+    if (sfile->fits && !psFitsClose(sfile->fits)) {
+        psError(PS_ERR_IO, false, "failed to close image to %s",
+            sfile->resolved_name);
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+    sfile->fits = NULL;
+    psFree(sfile->header);
+    sfile->header = NULL;
+    psFree(sfile->image);
+    sfile->image = NULL;
+    psFree(sfile->imagecube);
+    sfile->imagecube = NULL;
+}
+
+void
+closeImages(streakFiles *sf)
+{
+    if (sf->stage == IPP_STAGE_RAW) {
+        if (sf->view) {
+            sf->view->readout = -1;
+            pmFPAfileIOChecks(sf->config, sf->view, PM_FPA_AFTER);
+            sf->view->cell = -1;
+            pmFPAfileIOChecks(sf->config, sf->view, PM_FPA_AFTER);
+            sf->view->chip = -1;
+            pmFPAfileIOChecks(sf->config, sf->view, PM_FPA_AFTER);
+        }
+    }
+    closeImage(sf->inImage);
+    closeImage(sf->outImage);
+    closeImage(sf->recImage);
+    if (sf->inMask) {
+        closeImage(sf->inMask);
+        closeImage(sf->outMask);
+        closeImage(sf->recMask);
+    }
+    if (sf->inChMask) {
+        closeImage(sf->inChMask);
+        closeImage(sf->outChMask);
+        closeImage(sf->recChMask);
+    }
+    if (sf->inWeight) {
+        closeImage(sf->inWeight);
+        closeImage(sf->outWeight);
+        closeImage(sf->recWeight);
+    }
+}
+
+static void
+freeImages(streakFiles *sf)
+{
+    sFileFree(sf->inImage);
+    sFileFree(sf->outImage);
+    sFileFree(sf->recImage);
+    if (sf->inMask) {
+        sFileFree(sf->inMask);
+        sFileFree(sf->outMask);
+        sFileFree(sf->recMask);
+    }
+    if (sf->inChMask) {
+        sFileFree(sf->inChMask);
+        sFileFree(sf->outChMask);
+        sFileFree(sf->recChMask);
+    }
+    if (sf->inWeight) {
+        sFileFree(sf->inWeight);
+        sFileFree(sf->outWeight);
+        sFileFree(sf->recWeight);
+    }
+    if (sf->inSources) {
+        sFileFree(sf->inSources);
+        sFileFree(sf->outSources);
+    }
+}
+
+bool
+replicate(sFile *outFile, sFile *inFile)
+{
+    if (!outFile->inNebulous) {
+        return true;
+    }
+    nebServer *server = getNebServer(NULL);
+
+    char *user_copies = nebGetXattr(server, inFile->name, "user.copies");
+    bool free_user_copies = true;
+    if (user_copies == NULL) {
+        // input does not have replication requested
+        return true;
+    }
+    if (!nebSetXattr(server, outFile->name, "user.copies", user_copies,  NEB_REPLACE)) {
+        psError(PM_ERR_UNKNOWN, true, "nebSetXattr failed for %s\n%s", outFile->name, nebErr(server));
+        return false;
+    }
+    if (!nebReplicate(server, outFile->name, NULL, NULL)) {
+        psError(PM_ERR_UNKNOWN, true, "nebReplicate failed for %s\n%s", outFile->name, nebErr(server));
+        return false;
+    }
+    if (free_user_copies) {
+        nebFree(user_copies);
+    }
+    return true;
+}
+
+// for any of the outputs that are stored in Nebulous set their extended attributes
+// and replicate the files
+
+bool
+replicateOutputs(streakFiles *sfiles)
+{
+    if (!replicate(sfiles->outImage, sfiles->inImage)) {
+        psError(PM_ERR_SYS, false, "failed to replicate outImage.");
+        return false;
+    }
+
+    if (sfiles->outMask) {
+        if (!replicate(sfiles->outMask, sfiles->inMask)) {
+            psError(PM_ERR_SYS, false, "failed to replicate outImage.");
+            return false;
+        }
+    }
+    if (sfiles->outChMask) {
+        if (!replicate(sfiles->outChMask, sfiles->inChMask)) {
+            psError(PM_ERR_SYS, false, "failed to replicate outImage.");
+            return false;
+        }
+    }
+    if (sfiles->outWeight) {
+        if (!replicate(sfiles->outWeight, sfiles->inWeight)) {
+            psError(PM_ERR_SYS, false, "failed to replicate outImage.");
+            return false;
+        }
+    }
+
+    if (sfiles->outSources) {
+        if (!replicate(sfiles->outSources, sfiles->inSources)) {
+            psError(PM_ERR_SYS, false, "failed to replicate outSources.");
+            return false;
+        }
+    }
+
+//      XXX: replicate the recovery images (if in nebulous)
+//      perhaps whether we do that or not should be configurable.
+//      Sounds like we need a recipe
+
+    return true;
+}
+
+static bool
+swapOutputToInput(sFile *in, sFile *out)
+{
+
+    if (in->inNebulous) {
+        nebServer *server = getNebServer(NULL);
+
+        if (!nebSwap(server, in->name, out->name)) {
+            psError(PM_ERR_SYS, true, "failed to swap files for %s: %s.",
+                in->name, nebErr(server));
+            return false;
+        }
+    } else {
+        psString command = NULL;
+
+        // regular files. use mv creating a backup
+        // NOTE: -b is a gnu option and may not always be available
+        psStringAppend(&command, "mv -b --suffix=.bak %s %s", out->resolved_name, in->resolved_name);
+        int status = system(command);
+        if (status != 0) {
+            psError(PM_ERR_SYS, true, "%s failed with status %d.",
+                command, status);
+            psFree(command);
+            return false;
+        }
+        psFree(command);
+        // XXX: shouldn't I be doing mv in->resolved_name.bak out->resolved_name
+        // to complete the swap if !remove?
+    }
+
+    return true;
+}
+
+bool
+swapOutputsToInputs(streakFiles *sfiles)
+{
+    if (sfiles->outMask) {
+        if (!swapOutputToInput(sfiles->inMask, sfiles->outMask)) {
+            psError(PM_ERR_SYS, false, "failed to swap instances for Mask.");
+            return false;
+        }
+    }
+
+    if (sfiles->outWeight) {
+        if (!swapOutputToInput(sfiles->inWeight, sfiles->outWeight)) {
+            psError(PM_ERR_SYS, false, "failed to swap instances for Weight.");
+            return false;
+        }
+    }
+
+    if (sfiles->outChMask) {
+        if (!swapOutputToInput(sfiles->inChMask, sfiles->outChMask)) {
+            psError(PM_ERR_SYS, false, "failed to swap instances for chip mask.");
+            return false;
+        }
+    }
+
+    if (sfiles->outSources) {
+        if (!swapOutputToInput(sfiles->inSources, sfiles->outSources)) {
+            psError(PM_ERR_SYS, false, "failed to swap instances for sources.");
+            return false;
+        }
+    }
+
+    if (!swapOutputToInput(sfiles->inImage, sfiles->outImage)) {
+        psError(PM_ERR_SYS, false, "failed to swap instances for Image.");
+        return false;
+    }
+
+
+    return true;
+}
+
+static bool
+deleteFile(sFile *sfile)
+{
+    if (sfile->inNebulous) {
+        nebServer *server = getNebServer(NULL);
+        if (!nebDelete(server, sfile->name)) {
+            psError(PM_ERR_SYS, false, "failed to delete %s\n%s.", sfile->name,
+                nebErr(server));
+            return false;
+        }
+    } else {
+        if (unlink(sfile->resolved_name)) {
+            psError(PM_ERR_SYS, false, "failed to delete %s\n%s.", sfile->resolved_name,
+                strerror(errno));
+            return false;
+        }
+    }
+    return true;
+}
+
+bool
+deleteTemps(streakFiles *sfiles)
+{
+    if (sfiles->outMask) {
+        deleteFile(sfiles->outMask);
+    }
+
+    if (sfiles->outChMask) {
+        deleteFile(sfiles->outChMask);
+    }
+
+    if (sfiles->outWeight) {
+        deleteFile(sfiles->outWeight);
+    }
+
+    if (sfiles->outImage) {
+        deleteFile(sfiles->outImage);
+    }
+
+    if (sfiles->outSources) {
+        deleteFile(sfiles->outSources);
+    }
+
+    return true;
+}
+
+bool
+isExciseValue(double value, double exciseValue)
+{
+    if (isnan(exciseValue)) {
+        return isnan(value);
+    } else {
+        return value == exciseValue;
+    }
+}
+
+void
+setMaskedToNAN(streakFiles *sfiles, psU32 maskMask, bool printCounts)
+{
+        long maskedPixels = 0;
+        long nandPixels = 0;
+        long nandWeights = 0;
+
+        psImage *image = sfiles->outImage->image;
+        int imageType = image->type.type;
+        double exciseValue = sfiles->inImage->exciseValue;
+
+        psImage *mask = sfiles->inMask->image;
+        psImage *weight = NULL;
+        int weightType = 0;
+        double weightExciseValue = NAN;
+        if (sfiles->outWeight) {
+            weight = sfiles->outWeight->image;
+            weightType = weight->type.type;
+            weightExciseValue = sfiles->inWeight->exciseValue;
+        }
+
+        if (printCounts) {
+            psTimerStart("NAN_MASKED");
+        }
+
+        for (int y=0; y < sfiles->inImage->numRows; y++) {
+            for (int x=0; x < sfiles->inImage->numCols; x++) {
+                // these gets are not necessary, we could just set the pixels to nan
+                // but I want to get the counts
+                double imageVal = 0; // avoid compiler warning
+                if (imageType == PS_TYPE_F32) {
+                    imageVal  = image->data.F32[y][x];
+                } else if (imageType == PS_TYPE_S16) {
+                    imageVal = image->data.S16[y][x];
+                } else if (imageType == PS_TYPE_U16) {
+                    imageVal = image->data.U16[y][x];
+                } else {
+                    psError(PS_ERR_PROGRAMMING, true, "unexpected image type found: %d\n",
+                                imageType);
+                    streaksExit("", PS_EXIT_PROG_ERROR);
+                }
+                psU32 maskVal = 0;
+                if (sfiles->stage == IPP_STAGE_RAW) {
+                    unsigned int xChip, yChip;
+                    cellToChipInt(&xChip, &yChip, sfiles->astrom, x, y);
+                    maskVal = mask->data.PS_TYPE_IMAGE_MASK_DATA[yChip][xChip];
+                } else {
+                    maskVal = mask->data.PS_TYPE_IMAGE_MASK_DATA[y][x];
+                }
+                if (maskVal & maskMask) {
+                    ++maskedPixels;
+                    if (!isExciseValue(imageVal, exciseValue)) {
+                        ++nandPixels;
+                        if (imageType == PS_TYPE_F32) {
+                            image->data.F32[y][x] = exciseValue;
+                        } else if (imageType == PS_TYPE_S16) {
+                            image->data.S16[y][x] = exciseValue;
+                        } else if (imageType == PS_TYPE_U16) {
+                            image->data.U16[y][x] = exciseValue;
+                        } else {
+                            psError(PS_ERR_PROGRAMMING, true, "unexpected image type found: %d\n",
+                                imageType);
+                            streaksExit("", PS_EXIT_PROG_ERROR);
+                        }
+                    }
+                    if (weight) {
+                        if (weightType == PS_TYPE_F32) {
+                            double weightVal = weight->data.F32[y][x];
+                            if (!isnan(weightVal)) {
+                                ++nandWeights;
+                                weight->data.F32[y][x] = NAN;
+                            }
+                        } else if (weightType == PS_TYPE_F64) {
+                            double weightVal = weight->data.F64[y][x];
+                            if (!isnan(weightVal)) {
+                                ++nandWeights;
+                                weight->data.F64[y][x] = NAN;
+                            }
+                        } else if(weightType == PS_TYPE_S16) {
+                            double weightVal = weight->data.S16[y][x];
+                            if (!isExciseValue(weightVal, weightExciseValue)) {
+                                ++nandWeights;
+                                image->data.S16[y][x] = weightExciseValue;
+                            }
+                        } else if (weightType == PS_TYPE_U16) {
+                            double weightVal = weight->data.U16[y][x];
+                            if (!isExciseValue(weightVal, weightExciseValue)) {
+                                ++nandWeights;
+                                image->data.U16[y][x] = weightExciseValue;
+                            }
+                        } else {
+                            psError(PS_ERR_PROGRAMMING, true, "unexpected image type found: %d\n",
+                                    weightType);
+                                streaksExit("", PS_EXIT_PROG_ERROR);
+                        }
+                    }
+                }
+            }
+        }
+        if (printCounts) {
+            psLogMsg(sfiles->program_name, PS_LOG_INFO, "time to NAN mask pixels: %f\n", psTimerClear("NAN_MASKED"));
+            long totalPixels = image->numRows * image->numCols;
+            psLogMsg(sfiles->program_name, PS_LOG_INFO, "pixels:        %10ld\n", totalPixels);
+            psLogMsg(sfiles->program_name, PS_LOG_INFO, "masked pixels: %10ld %4.2f%%\n", maskedPixels, 100. * maskedPixels / totalPixels);
+            psLogMsg(sfiles->program_name, PS_LOG_INFO, "nand pixels:   %10ld %4.2f%%\n", nandPixels, 100. * nandPixels / totalPixels);
+            if (weight) {
+                psLogMsg(sfiles->program_name, PS_LOG_INFO, "nand weights:  %10ld %4.2f%%\n", nandWeights, 100. * nandWeights / totalPixels);
+            }
+        }
+}
+
+void
+streaksNebulousCleanup(void)
+{
+    nebServerFree(ourNebServer);
+    ourNebServer = NULL;
+}
+static void
+setExciseValue(sFile *in)
+{
+    if (in->image->type.type == PS_TYPE_U16) {
+        in->exciseValue = 65535;
+        psMetadataAddU16(in->header, PS_LIST_TAIL, "BLANK", 0, "", 65535);
+        psMetadataAddU16(in->header, PS_LIST_TAIL, "ZBLANK", 0, "", 65535);
+    } else if (in->image->type.type == PS_TYPE_S16) {
+        in->exciseValue = 32767;
+        psMetadataAddU16(in->header, PS_LIST_TAIL, "BLANK", 0, "", 32767);
+        psMetadataAddU16(in->header, PS_LIST_TAIL, "ZBLANK", 0, "", 32767);
+    } else if (in->image->type.type == PS_TYPE_F32) {
+        in->exciseValue = NAN;
+    } else if (in->image->type.type == PS_TYPE_F64) {
+        in->exciseValue = NAN;
+    } else {
+        psError(PS_ERR_PROGRAMMING, true, "unexpected image type found: %d\n", in->image->type.type);
+        streaksExit("", PS_EXIT_PROG_ERROR);
+    }
+}
+
+// Get the mask values that we need.
+// If an input mask file is provided get them from there.
+// Otherwise get them from the recipe MASKS
+void
+strkGetMaskValues(streakFiles *sfiles)
+{
+    if (sfiles->maskStreak) {
+        // already done
+        return;
+    }
+    if (!pmCensorGetMasks(sfiles->config, &sfiles->maskMask, &sfiles->maskStreak)) {
+        streaksExit("failed to find mask values", PS_EXIT_CONFIG_ERROR);
+    }
+}
+
+static void
+doCopyExtraExtensions(streakFiles *sf, sFile *in, sFile *out)
+{
+    for (int extnum = sf->extnum; extnum < in->nHDU; extnum++) {
+        moveExt(in, extnum);
+        readImage(in, extnum, sf->stage, false);
+
+        if (SFILE_IS_IMAGE(in)) {
+            // Turn off compression (code adapted from pmFPAWrite)
+            int bitpix = out->fits->options ? out->fits->options->bitpix : 0; // Desired bits per pixel
+            psFitsCompression *compress = psFitsCompressionGet(out->fits); // Current compression options
+            if (!psFitsSetCompression(out->fits, PS_FITS_COMPRESS_NONE, NULL, 0, 0, 0)) {
+                psError(PM_ERR_UNKNOWN, false, "failed to turn off compression for extension %d\n", extnum);
+                streaksExit("", PS_EXIT_UNKNOWN_ERROR);
+            }
+            if (out->fits->options) {
+                out->fits->options->bitpix = 0;
+            }
+            if (!psFitsWriteImage(out->fits, in->header, in->image, 0, in->extname)) {
+                psError(PM_ERR_UNKNOWN, false, "failed to write image for extension %d\n", extnum);
+                streaksExit("", PS_EXIT_UNKNOWN_ERROR);
+            }
+            if (out->fits->options) {
+                out->fits->options->bitpix = bitpix;
+            }
+            if (!psFitsCompressionApply(out->fits, compress)) {
+                psError(PM_ERR_UNKNOWN, false, "failed to rest compression image for extension %d\n", extnum);
+                streaksExit("", PS_EXIT_UNKNOWN_ERROR);
+            }
+            psFree(compress);
+        } else {
+            copyTable(out, in, extnum);
+        }
+    }
+}
+
+void
+copyExtraExtensions(streakFiles *sf)
+{
+    // XXX: Bad assumption, the begining of the mask and weight files have the same structure as the image
+    // So we are currently at the image portion
+    sf->extnum = sf->inImage->nHDU;
+
+    if (sf->inWeight && sf->outWeight) {
+        doCopyExtraExtensions(sf, sf->inWeight, sf->outWeight);
+    }
+    if (sf->inMask && sf->outMask) {
+        doCopyExtraExtensions(sf, sf->inMask, sf->outMask);
+    }
+    if (sf->inChMask && sf->outChMask) {
+        doCopyExtraExtensions(sf, sf->inChMask, sf->outChMask);
+    }
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksio.h
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksio.h	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksio.h	(revision 41117)
@@ -0,0 +1,39 @@
+#ifndef STREAKS_IO_H
+#define STREAKS_IO_H 1
+
+streakFiles *openFiles(pmConfig *config, bool remove, char *program_name);
+
+sFile *sFileOpen(pmConfig *config, ippStage stage, psString fileSelect,
+    psString outputFilename, bool required, bool checkIfDestreaked);
+
+void closeImages(streakFiles *sfiles);
+
+void closeImage(sFile *sfile);
+
+void readImage(sFile *sfile, int extnum, ippStage stage, bool isMask);
+void copyPHU(streakFiles *sfiles, bool remove, bool destreaked);
+void copyTable(sFile *out, sFile *in, int extnum);
+void copyFitsOptions(sFile *out, sFile *rec, sFile *in, psVector *tiles);
+void setupImageRefs(sFile *out, sFile *recoveryOut, sFile *in, int extnum, bool exciseAll);
+void strkGetMaskValues(streakFiles *sfiles);
+void copyExtraExtensions(streakFiles *sfiles);
+
+void writeImage(sFile *sfile, psString extname, int extnum);
+void writeImageCube(sFile *sfile, psArray *imagecube, psString extname, int extnum);
+bool replicate(sFile *outFile, sFile *inFile);
+void readImageFrom_pmFile(streakFiles *sf);
+
+void addDestreakKeyword(psMetadata *, bool value);
+void addRecoveryKeyword(psMetadata *);
+
+bool streakFilesNextExtension(streakFiles *sf);
+
+bool isExciseValue(double, double);
+void setMaskedToNAN(streakFiles *sfiles, psU32 maskMask, bool printCounts);
+
+bool replicateOutputs(streakFiles *sf);
+bool swapOutputsToInputs(streakFiles *sf);
+bool deleteTemps(streakFiles *sf);
+void streaksNebulousCleanup(void);
+
+#endif // STREAKS_IO_H
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksrelease.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksrelease.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksrelease.c	(revision 41117)
@@ -0,0 +1,316 @@
+#include "streaksremove.h"
+
+static pmConfig *parseArguments(int argc, char **argv);
+static bool readAndCopyToOutput(streakFiles *sf, bool exciseAll);
+static void writeImages(streakFiles *sf, bool exciseImageCube);
+
+char *streaksProgram = "streaksrelease";
+
+int
+main(int argc, char *argv[])
+{
+    psLibInit(NULL);
+    psTimerStart("STREAKSREMOVE");
+
+    pmConfig *config = parseArguments(argc, argv);
+    if (!config) {
+        psError(PS_ERR_UNKNOWN, false, "failed to parse arguments\n");
+        return PS_EXIT_CONFIG_ERROR;
+    }
+
+    // Does true work here?
+    streakFiles *sfiles = openFiles(config, true, argv[0]);
+
+    if (sfiles->stage == IPP_STAGE_RAW) {
+        // copy PHU to output files
+        copyPHU(sfiles, false, true);
+
+        // advance to the first image extension
+        if (!streakFilesNextExtension(sfiles)) {
+            psErrorStackPrint(stderr, "failed to advance to first extension of input images");
+            exit (PS_EXIT_PROG_ERROR);
+        }
+    }
+
+    // Iterate through each component of the input (there is only one except for raw images)
+    do {
+        psTimerStart("PROCESS_COMPONENT");
+
+        // read the images and copy the data from the inputs to the outputs
+        // This also sets up the astrometry and initializes the recovery image(s)
+
+        if (!readAndCopyToOutput(sfiles, false)) {
+            // false return value indicates that this extension is not an image and
+            // the contents has already been copied to the output file.
+            // we don't need to process this extesion for streaks.
+            continue;
+        }
+
+        // now that we've read the input files, lookup the mask values that we read
+        strkGetMaskValues(sfiles);
+
+        if (!sfiles->inImage->imagecube) {
+            setMaskedToNAN(sfiles, sfiles->maskMask, true);
+        }
+
+        // write out the destreaked temporary images and the recovery images
+        writeImages(sfiles, false);
+
+        printf("time to process component %d: %f\n", sfiles->extnum, psTimerClear("PROCESS_COMPONENT"));
+    } while (streakFilesNextExtension(sfiles));
+
+    // check the weight and mask files for extra extensions that might be in files
+    // (covariance matrix for example)
+    copyExtraExtensions(sfiles);
+
+
+
+    psTimerStart("CLOSE_IMAGES");
+    // close all files
+    closeImages(sfiles);
+    printf("time to close images: %f\n", psTimerClear("CLOSE_IMAGES"));
+
+    printf("time to run streaksrelease: %f\n", psTimerClear("STREAKSREMOVE"));
+
+    // nebServerFree(ourNebServer);
+    psFree(config);
+    pmConceptsDone();
+    pmConfigDone();
+    psLibFinalize();
+
+    //  PAU
+    fprintf(stderr, "Found %d leaks at %s\n", psMemCheckLeaks (0, NULL, NULL, false), "streaksrelease");
+
+
+    return 0;
+}
+
+static void usage()
+{
+    psErrorStackPrint(stderr, "\n");
+    fprintf(stderr, "USAGE: streaksrelease -stage raw|chip|warp|diff -image IMAGE.fits -outroot directory\n\n");
+    fprintf(stderr, "Other arguments:\n");
+    fprintf(stderr, "\t-class_id class_id: class_id (required for raw and chip stages)\n");
+    fprintf(stderr, "\t-mask MASK.fits mask file to de-streak\n");
+    fprintf(stderr, "\t-weight WEIGHT.fits: weight file to de-streak\n");
+    fprintf(stderr, "\t-chip_mask MASK.fits: name of mask for chip stage (camera stage mask is passed tih -mask)\n");
+
+    exit(2);
+}
+
+static pmConfig *parseArguments(int argc, char **argv)
+{
+
+    pmConfig *config = pmConfigRead(&argc, argv, NULL);
+    if (!config) {
+        streaksExit("failed to parse arguments", PS_EXIT_CONFIG_ERROR);
+    }
+
+    int argnum;
+    ippStage stage = IPP_STAGE_NONE;
+    if ((argnum = psArgumentGet(argc, argv, "-stage"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        stage = parseStage(argv[argnum]);
+        psMetadataAddS32(config->arguments, PS_LIST_TAIL, "STAGE", 0,
+                "pipeline stage for streak removal", stage);
+        psArgumentRemove(argnum, &argc, argv);
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "-stage is required\n");
+        usage();
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-class_id"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "CLASS_ID", 0,
+                "class_id", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    } else if ((stage == IPP_STAGE_RAW) || (stage == IPP_STAGE_CHIP)) {
+        psError(PS_ERR_UNKNOWN, true, "-class_id is required\n");
+        usage();
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-image"))) {
+        // for raw and chip level images we use psFits for I/O and ...
+        if (CHIP_LEVEL_INPUT(stage)) {
+            psArgumentRemove(argnum, &argc, argv);
+            psMetadataAddStr(config->arguments, PS_LIST_TAIL, "INPUT", 0,
+                    "name of input image", argv[argnum]);
+            psArgumentRemove(argnum, &argc, argv);
+        } else  {
+            // ... for warped images we use pmFPAfiles
+            if (!pmConfigFileSetsMD(config->arguments, &argc, argv, "INPUT", "-image", NULL)) { ;
+                psError(PS_ERR_UNKNOWN, false, "failed to process -image");
+                usage();
+            }
+        }
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "-image is required\n");
+        usage();
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-mask"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "INPUT.MASK", 0,
+                "name of input mask image", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-chip_mask"))) {
+        if (stage != IPP_STAGE_CHIP) {
+            psError(PS_ERR_UNKNOWN, true, "-chip_mask only allowed at chip stage");
+            usage();
+        }
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "INPUT.CHMASK", 0,
+                "name of input chip mask image", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-weight"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "INPUT.WEIGHT", 0,
+                "name of input weight image", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-outroot"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psString dir = pathToDirectory(argv[argnum]);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "directory for output files",
+            dir);
+        psArgumentRemove(argnum, &argc, argv);
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "-outroot is required\n");
+        usage();
+    }
+
+    if (argc != 1) {
+        psString unexpectedArguments = NULL;
+        for (int i=1; i<argc; i++) {
+            psStringAppend(&unexpectedArguments, "%s ", argv[i]);
+        }
+        psError(PS_ERR_UNKNOWN, true, "unexpected arguments: %s", unexpectedArguments);
+        psFree(unexpectedArguments);
+        usage();
+    }
+
+    return config;
+}
+
+// set the image for the output files to the contents of the corresponding input file.
+static bool
+readAndCopyToOutput(streakFiles *sf, bool exciseAll)
+{
+    if (sf->inImage->pmfile) {
+        // image data from pmFPAfile (diff or warp file)
+        readImageFrom_pmFile(sf);
+    } else {
+        // image data directly from psFits
+        readImage(sf->inImage, sf->extnum, sf->stage, false);
+
+        // astrom struct is only needed for raw stage, and only the chip to cell parameters are used
+        sf->astrom = streakSetAstrometry(sf->astrom, sf->stage, NULL, NULL, false,
+            sf->inImage->header, sf->inImage->numCols, sf->inImage->numRows);
+    }
+    sf->outImage->header = (psMetadata*) psMemIncrRefCounter(sf->inImage->header);
+
+    if (!SFILE_IS_IMAGE(sf->inImage)) {
+        // fprintf(stderr, "skipping extnum: %d not an image\n", sf->extnum);
+        // XXX: This extension is probably a video table.
+        copyTable(sf->outImage, sf->inImage, sf->extnum);
+        return false;
+    }
+
+    if (sf->inImage->image) {
+        setupImageRefs(sf->outImage, sf->recImage, sf->inImage, sf->extnum, exciseAll);
+    }  else {
+        // image cubes are handled specially
+    }
+
+    // set up the compression parameters
+    copyFitsOptions(sf->outImage, sf->recImage, sf->inImage, sf->tiles);
+
+    if (sf->inMask) {
+        readImage(sf->inMask, sf->extnum, sf->stage, true);
+        if (sf->outMask) {
+            sf->outMask->header = (psMetadata*) psMemIncrRefCounter(sf->inMask->header);
+            if (sf->recMask) {
+                sf->recMask->header = (psMetadata*) psMemIncrRefCounter(sf->inMask->header);
+            }
+            setupImageRefs(sf->outMask, sf->recMask, sf->inMask, sf->extnum, exciseAll);
+            if (sf->outChMask) {
+                sf->outChMask->header = (psMetadata *) psMemIncrRefCounter(sf->outMask->header);
+                sf->outChMask->image = (psImage *) psMemIncrRefCounter(sf->outMask->image);
+                if (sf->recChMask) {
+                    sf->recChMask->header = (psMetadata *) psMemIncrRefCounter(sf->recMask->header);
+                    sf->recChMask->image = (psImage *) psMemIncrRefCounter(sf->recMask->image);
+                }
+            }
+
+            copyFitsOptions(sf->outMask, sf->recMask, sf->inMask, sf->tiles);
+            if (sf->outChMask) {
+                copyFitsOptions(sf->outChMask, sf->recChMask, sf->inMask, sf->tiles);
+            }
+        }
+    }
+
+    if (sf->inWeight) {
+        readImage(sf->inWeight, sf->extnum, sf->stage, false);
+        sf->outWeight->header = (psMetadata*) psMemIncrRefCounter(sf->inWeight->header);
+        if (sf->recWeight) {
+            sf->recWeight->header = (psMetadata*) psMemIncrRefCounter(sf->inWeight->header);
+        }
+        setupImageRefs(sf->outWeight, sf->recWeight, sf->inWeight, sf->extnum, exciseAll);
+
+        copyFitsOptions(sf->outWeight, sf->recWeight, sf->inWeight, sf->tiles);
+    }
+    // and for raw images, create sub images that represent the actual image
+    // area (no overscan)
+
+    return true;
+}
+
+
+
+// XXXX: why isn't this in streaksio.c ?? See also streakremove.c
+static void
+writeImages(streakFiles *sf, bool exciseImageCube)
+{
+    psString extname = NULL;
+    if (sf->nHDU > 1) {
+        bool mdok;
+        extname = psMetadataLookupStr(&mdok, sf->inImage->header, "EXTNAME");
+    }
+    if (sf->inImage->numZPlanes == 0)  {
+        // note exciseing complete images is handled in readAndCopyToOutput
+        writeImage(sf->outImage, extname, sf->extnum);
+        writeImage(sf->recImage, extname, sf->extnum);
+    } else {
+        // we have an image cube
+        // double initValue;
+        if (exciseImageCube) {
+            // copy the entire input image to the recovery image
+            writeImageCube(sf->recImage, sf->inImage->imagecube, extname, sf->extnum);
+            // initValue = NAN;
+        } else {
+            // otherwise write it to the output
+            writeImageCube(sf->outImage, sf->inImage->imagecube, extname, sf->extnum);
+            // initValue = 0;
+        }
+
+        // Assumption: there are no mask and weight images for video cells
+        return;
+    }
+    if (sf->outMask) {
+        writeImage(sf->outMask, extname, sf->extnum);
+        writeImage(sf->recMask, extname, sf->extnum);
+        if (sf->stage == IPP_STAGE_CHIP) {
+            writeImage(sf->outChMask, extname, sf->extnum);
+            writeImage(sf->recChMask, extname, sf->extnum);
+        }
+    }
+    if (sf->outWeight) {
+        writeImage(sf->outWeight, extname, sf->extnum);
+        writeImage(sf->recWeight, extname, sf->extnum);
+    }
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksremove.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksremove.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksremove.c	(revision 41117)
@@ -0,0 +1,1089 @@
+/*
+ * streaksremove
+ *
+ * Convert satellite streak detctions into masks and remove the covered pixels from the
+ * input images.
+ * Optionally swap the inputs with the outputs so that subsequent references to the original
+ * images access the destreaked versions.
+ */
+
+#include "streaksremove.h"
+
+static pmConfig *parseArguments(int argc, char **argv);
+static bool readAndCopyToOutput(streakFiles *sf, bool exciseAll);
+static long exciseNonDiffedPixels(streakFiles *sfiles, psImageMaskType newMaskValue);
+static bool diffedPixel(streakFiles *sfiles, int x, int y);
+static void excisePixel(streakFiles *sfiles, unsigned int x, unsigned int y, bool streak, psImageMaskType newMaskValue);
+static void writeImages(streakFiles *sf, bool exciseImageCube);
+static void updateAstrometry(streakFiles *sfiles);
+static void censorSources(streakFiles *sfiles, psImageMaskType maskStreak);
+static long censorPixels(streakFiles *sfiles, psImage * pixels, bool checkNonDiffedPixels, psU16 maskStreak);
+
+char *streaksProgram = "streaksremove";
+
+// Note: For clarity the flow of this program is in main().
+// There is not a lot of error checking is done in main.
+// Until the end, where we might be doing Nebulous operations, called functions exit when an error
+// is encountered.
+int
+main(int argc, char *argv[])
+{
+    bool status;
+
+    psLibInit(NULL);
+    psTimerStart("TOTAL_TIME");
+
+    pmConfig *config = parseArguments(argc, argv);
+    if (!config) {
+        psError(PS_ERR_UNKNOWN, false, "failed to parse arguments\n");
+        streaksExit("", PS_EXIT_CONFIG_ERROR);
+    }
+
+    psString streaksFileName = psMetadataLookupStr(NULL, config->arguments, "STREAKS");
+
+    // call Paul Sydney's code to parse the streaks file that DetectStreaks produced
+    Streaks *streaks = readStreaksFile(streaksFileName);
+    if (!streaks) {
+        psError(PS_ERR_UNKNOWN, false, "failed to read streaks file: %s", streaksFileName);
+        streaksExit("", PS_EXIT_PROG_ERROR);
+    }
+
+    // open all of the input and output files, save their descriptions in the streakFiles struct
+    streakFiles *sfiles = openFiles(config, true, argv[0]);
+    setupAstrometry(sfiles);
+    sfiles->stats = psMetadataAlloc();
+
+    // Optionally we can set pixels that are masked to NAN since they couldn't have been
+    // examined for streaks. Usually this is done by the distribution system just prior
+    // to release
+    bool nanForRelease = psMetadataLookupBool(&status, config->arguments, "NAN_FOR_RELEASE");
+    if (nanForRelease && (sfiles->inMask == NULL)) {
+        psError(PS_ERR_UNKNOWN, true, "mask image must be supplied with -release.");
+        streaksExit("", PS_EXIT_PROG_ERROR);
+    }
+
+    bool exciseAll = false;
+    // --keepnondiffed is a test and debug mode
+    bool keepNonDiffedPixels = psMetadataLookupBool(&status, config->arguments, "KEEP_NON_DIFFED");
+
+    // we need to check for non diffed pixels unless we've been asked not to or the stage is diff
+    // (By definition pixels in diff images were included in the difference images)
+    bool checkNonDiffedPixels = ! (keepNonDiffedPixels || (sfiles->stage == IPP_STAGE_DIFF) );
+
+    if (checkNonDiffedPixels ) {
+        // From magic ICD:
+        // In the raw and detrended images, the pixels which were not
+        // included in any of the streak-processed warps must also be masked.
+        // Note that the warp and difference skycells are only generated if more
+        // than a small fraction of the pixels are lit by the input image.
+        // if no skycells are provided sfiles->exciseAll is set to true
+
+        psTimerStart("COMPUTE_DIFFED_PIXELS");
+        if (! computeDiffedPixels(sfiles) ) {
+            // we have no choice to excise all pixels
+            exciseAll = true;
+        }
+        psF64 cdp_t = psTimerClear("COMPUTE_DIFFED_PIXELS");
+        psMetadataAddF32(sfiles->stats, PS_LIST_TAIL, "COMPUTE_UNDIFFED_PIXELS", PS_META_REPLACE, "time to compute non-diffedpixels", cdp_t);
+        psLogMsg("streaksremove", PS_LOG_INFO, "time to compute diffed pixels: %f\n", cdp_t);
+    }
+
+    if (sfiles->stage == IPP_STAGE_RAW) {
+        // Except for raw stage, all of our (GPC1) files have one image extension.
+        // Raw files have a phu and multiple extensions, one per chip
+        // Since this is a raw file, copy it's PHU to output files
+        copyPHU(sfiles, true, true);
+
+        // advance to the first image extension
+        if (!streakFilesNextExtension(sfiles)) {
+            psErrorStackPrint(stderr, "failed to advance to first extension of input images");
+            exit (PS_EXIT_PROG_ERROR);
+        }
+    }
+
+    long totalPixels = 0;
+    long totalStreakPixels = 0;
+    long nonDiffedPixels = 0;
+
+    // accumulators for the various timers
+    psF64 gsp_t = 0;
+    psF64 enw_t = 0;
+    psF64 rms_t = 0;
+    psF64 cs_t = 0;
+    psF64 wi_t = 0;
+    psF64 ua_t = 0;
+    // Iterate through each component of the input (except for raw images there is only one)
+    do {
+        bool exciseImageCube = false;
+
+        psTimerStart("PROCESS_COMPONENT");
+
+        // read the images and copy the data from the inputs to the outputs
+        // This also sets up the astrometry and initializes the recovery image(s)
+
+        if (!readAndCopyToOutput(sfiles, exciseAll)) {
+            // false return value indicates that this extension is not an image and
+            // the contents has already been copied to the output file.
+            // we don't need to process this extesion for streaks.
+            continue;
+        }
+
+        // now that we've read the input files, lookup the mask values
+        strkGetMaskValues(sfiles);
+
+        totalPixels += sfiles->inImage->numRows * sfiles->inImage->numCols;
+
+        if (!exciseAll) {
+            // Identify pixels to mask because of streaks
+
+            psTimerStart("GET_STREAK_PIXELS");
+
+            // call Paul Sydney's code to compute the set of pixels that are covered by the detected streaks
+            StreakPixels *pixels = streak_on_component(streaks, sfiles->astrom, sfiles->inImage->numCols,
+                                                        sfiles->inImage->numRows);
+            gsp_t +=  psTimerClear("GET_STREAK_PIXELS");
+            psMetadataAddF32(sfiles->stats, PS_LIST_TAIL, "GET_STREAK_PIXELS", PS_META_REPLACE, "", gsp_t);
+            psLogMsg("streaksremove", PS_LOG_INFO, "time to get streak pixels: %f\n", psTimerClear("GET_STREAK_PIXELS"));
+
+            // if this extension contained an image, excise the streaked pixels.
+            // otherwise it contained an image cube (video cell) which is handled in the if block
+            if (sfiles->inImage->image) {
+                if (checkNonDiffedPixels) {
+                    psTimerStart("EXCISE_NON_DIFFED");
+
+                    // set non-diffed pixels and variance to NAN, mask to maskStreak (since the pixel
+                    // is excised as part of the destreaking process)
+                    nonDiffedPixels += exciseNonDiffedPixels(sfiles, sfiles->maskStreak);
+
+                    enw_t +=  psTimerClear("EXCISE_NON_DIFFED");
+                    psMetadataAddF32(sfiles->stats, PS_LIST_TAIL, "EXCISE_NON_DIFFED", PS_META_REPLACE, "", enw_t);
+                    psLogMsg("streaksremove", PS_LOG_INFO, "time to excise non diffed pixels: %f\n", enw_t);
+                }
+
+                psTimerStart("REMOVE_STREAKS");
+
+                totalStreakPixels += censorPixels(sfiles, pixels, checkNonDiffedPixels, sfiles->maskStreak);
+
+                rms_t += psTimerClear("REMOVE_STREAKS");
+                psMetadataAddF32(sfiles->stats, PS_LIST_TAIL, "REMOVE_STREAKS", PS_META_REPLACE, "", enw_t);
+                psLogMsg("streaksremove", PS_LOG_INFO, "time to remove streak pixels: %f\n", rms_t);
+
+                if (nanForRelease) {
+                    // set any pixels that were masked, to NAN (unless they are already NAN)
+                    setMaskedToNAN(sfiles, sfiles->maskMask, true);
+                }
+
+            } else {
+                // this component contains an image cube
+                // For now excise it completely
+                exciseImageCube = true;
+            }
+            psFree(pixels);
+        }
+
+        if (sfiles->stage == IPP_STAGE_CHIP) {
+            // as a convience to the user of the output, replace the bogus WCS transform in the
+            // chip processed files with the data calcuated by psastro at the camera stage
+            // (actually we use a linear approximation)
+            psTimerStart("UPDATE_ASTROMETRY");
+            updateAstrometry(sfiles);
+            ua_t += psTimerClear("UPDATE_ASTROMETRY");
+            psMetadataAddF32(sfiles->stats, PS_LIST_TAIL, "UPDATE_ASTROMETRY", PS_META_REPLACE, "", ua_t);
+            psLogMsg("streaksremove", PS_LOG_INFO, "time to update astrometry: %f\n", ua_t);
+        }
+
+        psTimerStart("CENSOR_SOURCES");
+        censorSources(sfiles, sfiles->maskStreak);
+        cs_t += psTimerClear("CENSOR_SOURCES");
+        psMetadataAddF32(sfiles->stats, PS_LIST_TAIL, "CENSOR_SOURCES", PS_META_REPLACE, "", cs_t);
+
+        // write the destreaked "temporary" images and the recovery images
+        psTimerStart("WRITE_IMAGES");
+        writeImages(sfiles, exciseImageCube);
+        wi_t += psTimerClear("WRITE_IMAGES");
+        psMetadataAddF32(sfiles->stats, PS_LIST_TAIL, "WRITE_IMAGES", PS_META_REPLACE, "", wi_t);
+
+        psLogMsg("streaksremove", PS_LOG_INFO, "time to process component %d: %f\n", sfiles->extnum, psTimerClear("PROCESS_COMPONENT"));
+
+    } while (streakFilesNextExtension(sfiles));
+
+
+    psFree(streaks);
+
+    if (exciseAll) {
+        totalStreakPixels = totalPixels;
+    }
+
+    psF64 streakFraction = (double) totalStreakPixels / totalPixels;
+    psLogMsg("streaksremove", PS_LOG_INFO, "   total pixels:  %8ld\n", totalPixels);
+    psLogMsg("streaksremove", PS_LOG_INFO, "  streak pixels:  %8ld %4.2f%%\n", totalStreakPixels, streakFraction * 100);
+    psMetadataAddF32(sfiles->stats, PS_LIST_TAIL, "STREAK_FRACTION", PS_META_REPLACE, "", streakFraction);
+
+    psF64 nonDiffedFraction = (double) nonDiffedPixels / totalPixels;
+    psLogMsg("streaksremove", PS_LOG_INFO, "nondiffed pixels:  %8ld %4.2f%%\n", nonDiffedPixels, nonDiffedFraction * 100);
+    psMetadataAddF32(sfiles->stats, PS_LIST_TAIL, "NONDIFFED_FRACTION", PS_META_REPLACE, "", nonDiffedFraction);
+
+    // check the weight and mask files for extra extensions that might be in files
+    // (covariance matrix for example)
+    copyExtraExtensions(sfiles);
+
+    // all done close the files. This is where the files are written so it can take a long time.
+
+    psTimerStart("CLOSE_IMAGES");
+    closeImages(sfiles);
+    psF64 ci_t = psTimerClear("CLOSE_IMAGES");
+    psMetadataAddF32(sfiles->stats, PS_LIST_TAIL, "CLOSE_IMAGES", PS_META_REPLACE, "", ci_t);
+
+    psLogMsg("streaksremove", PS_LOG_INFO, "time to close images: %f\n", ci_t);
+
+#ifdef DO_REPLICATE
+    psTimerStart("REPLICATE_OUTPUTS");
+    if (!replicateOutputs(sfiles)) {
+        psErrorStackPrint(stderr, "failed to replicate output files");
+        deleteTemps(sfiles);
+        exit(PS_EXIT_UNKNOWN_ERROR);
+    }
+    psF64 ro_t = psTimerClear("REPLICATE_OUTPUTS");
+    psMetadataAddF32(sfiles->stats, PS_LIST_TAIL, "REPLICATE_OUTPUTS", PS_META_REPLACE, "", ro_t);
+#endif
+
+    // NOTE: from here on we can't just quit if something goes wrong.
+    // especially if we're working at the raw stage
+    // turn off automatic deletion of output files by streaksExit
+    setStreakFiles(NULL);
+
+    if (psMetadataLookupBool(&status, config->arguments, "REPLACE")) {
+        //     swap the instances for the input and output
+        //     Note this is a nebulous database operation. No file I/O is performed
+        psTimerStart("SWAP_INSTANCES");
+        if (!swapOutputsToInputs(sfiles)) {
+            // It is up to the program that reverts failed destreak runs to insure that
+            // any original non-destreaked input files that have been swapped are restored and that the de-streaked
+            // versions are deleted.
+
+            psErrorStackPrint(stderr, "failed to swap files");
+
+            // XXX: pick a specific error code for this failure
+            exit(PS_EXIT_UNKNOWN_ERROR);
+        }
+        psF64 si_t = psTimerClear("SWAP_INSTANCES");
+        psMetadataAddF32(sfiles->stats, PS_LIST_TAIL, "SWAP_INSTANCES", PS_META_REPLACE, "", si_t);
+    }
+
+    psF64 total_time = psTimerClear("TOTAL_TIME");
+    psMetadataAddF32(sfiles->stats, PS_LIST_TAIL, "TOTAL_TIME", PS_META_REPLACE, "", total_time);
+    psLogMsg("streaksremove", PS_LOG_INFO, "time to run streaksremove: %f\n", total_time);
+
+    psString statsFileName= psMetadataLookupStr(&status, config->arguments, "STATS");
+
+    if (statsFileName) {
+        psString resolved = pmConfigConvertFilename(statsFileName, config, true, true); // Resolved filename
+        if (!resolved) {
+            psError(psErrorCodeLast(), false, "Unable to resolve statistics file name");
+            return false;
+        }
+        if (!psMetadataConfigWrite(sfiles->stats, resolved, NULL)) {
+            psError(psErrorCodeLast(), false, "Unable to serialize stats metadata.\n");
+            psFree(resolved);
+            return false;
+        }
+        psFree(resolved);
+    }
+
+    // all done. Clean up and look for memory leaks.
+
+    psFree(sfiles);
+    psFree(config);
+    pmConceptsDone();
+    pmModelClassCleanup();
+    streaksNebulousCleanup();
+    pmConfigDone();
+    psLibFinalize();
+
+    int leaks = psMemCheckLeaks (0, NULL, stdout, false);
+
+    fprintf(stderr, "Found %d leaks at %s\n", leaks, "streaksremove");
+
+    return 0;
+}
+
+static long
+censorPixels(streakFiles *sfiles, psImage *pixels, bool checkNonDiffedPixels, psU16 maskStreak)
+{
+    long streakPixels = 0;
+
+    for (int y=0 ; y < sfiles->inImage->numRows; y++) {
+        for (int x = 0; x < sfiles->inImage->numCols; x++) {
+            if (psImageGet(pixels, x, y)) {
+                if (!checkNonDiffedPixels || diffedPixel(sfiles, x, y)) {
+                    ++streakPixels;
+
+                    excisePixel(sfiles, x, y, true, maskStreak);
+
+                } else {
+                    // This pixel was not included in any warp and has thus already excised
+                    // by exciseNonDiffedPixels
+                }
+            }
+        }
+    }
+    return streakPixels;
+}
+
+static void usage()
+{
+    fprintf(stderr, "USAGE: streaksremove -stage raw|chip|warp|diff -image IMAGE.fits -tmproot directory -streaks streaks_file\n\n");
+    fprintf(stderr, "Optional arguments:\n");
+    fprintf(stderr, "\t-class_id class_id: class_id (required for raw and chip stages)\n");
+    fprintf(stderr, "\t-recovery: directory to save excised pixel images\n");
+    fprintf(stderr, "\t-mask MASK.fits mask file to de-streak\n");
+    fprintf(stderr, "\t-weight WEIGHT.fits: weight file to de-streak\n");
+    fprintf(stderr, "\t-replace: replace the input images with the output\n");
+    fprintf(stderr, "\t-keepnondiffed: do not exise pixels that were not part of difference processing\n");
+    fprintf(stderr, "\t-transparent val: instead of setting excicsed pixel to NAN add val\n");
+    fprintf(stderr, "\t-chip_mask MASK.fits: name of mask for chip stage (camera stage mask is passed tih -mask)\n");
+
+    exit(2);
+
+
+}
+static pmConfig *parseArguments(int argc, char **argv)
+{
+
+    pmConfig *config = pmConfigRead(&argc, argv, NULL);
+    if (!config) {
+        streaksExit("failed to parse arguments", PS_EXIT_CONFIG_ERROR);
+    }
+
+    int argnum;
+    ippStage stage = IPP_STAGE_NONE;
+    if ((argnum = psArgumentGet(argc, argv, "-stage"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        stage = parseStage(argv[argnum]);
+        psMetadataAddS32(config->arguments, PS_LIST_TAIL, "STAGE", 0,
+                "pipeline stage for streak removal", stage);
+        psArgumentRemove(argnum, &argc, argv);
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "-stage is required\n");
+        usage();
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-class_id"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "CLASS_ID", 0,
+                "class_id", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    } else if ((stage == IPP_STAGE_RAW) || (stage == IPP_STAGE_CHIP)) {
+        psError(PS_ERR_UNKNOWN, true, "-class_id is required\n");
+        usage();
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-streaks"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "STREAKS", 0,
+                "STREAKS", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "-streaks is required\n");
+        usage();
+    }
+
+
+    // If input image is in nebulous we require mask, weight, and tmproot to all be
+    // nebulous paths
+    bool nebulousImage = false;
+    if ((argnum = psArgumentGet(argc, argv, "-image"))) {
+        // for raw and chip level images we use psFits for I/O and ...
+        nebulousImage = IN_NEBULOUS(argv[argnum+1]);
+        if (CHIP_LEVEL_INPUT(stage)) {
+            psArgumentRemove(argnum, &argc, argv);
+            psMetadataAddStr(config->arguments, PS_LIST_TAIL, "INPUT", 0,
+                    "name of input image", argv[argnum]);
+            psArgumentRemove(argnum, &argc, argv);
+        } else  {
+            // ... for warped images we use pmFPAfiles
+            if (!pmConfigFileSetsMD(config->arguments, &argc, argv, "INPUT", "-image", NULL)) { ;
+                psError(PS_ERR_UNKNOWN, false, "failed to process -image");
+                usage();
+            }
+        }
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "-image is required\n");
+        usage();
+    }
+
+    bool gotReplace = false;
+    if ((argnum = psArgumentGet(argc, argv, "-replace"))) {
+        if (!nebulousImage) {
+            psError(PS_ERR_UNKNOWN, true, "replace is only supported for nebulous files");
+            usage();
+        }
+        gotReplace = true;
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddBool(config->arguments, PS_LIST_TAIL, "REPLACE", 0, "replace input files",
+            true);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-release"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddBool(config->arguments, PS_LIST_TAIL, "NAN_FOR_RELEASE", 0, "set masked pixels to NAN",
+            true);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-keepnondiffed"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddBool(config->arguments, PS_LIST_TAIL, "KEEP_NON_DIFFED", 0,
+            "skip excising of non diffed pixels", true);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-transparent"))) {
+        if (gotReplace) {
+            psError(PS_ERR_UNKNOWN, true, "cannot make transparent streaks if -replace");
+            usage();
+        }
+        psArgumentRemove(argnum, &argc, argv);
+        double transparentStreaks = atof(argv[argnum]);
+        psMetadataAddF32(config->arguments, PS_LIST_TAIL, "TRANSPARENT_STREAKS", 0,
+            "value to adjust excised pixels", transparentStreaks);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    // if skycells are not provided then we have to execise all pixels  unless -keepnondiffed
+    pmConfigFileSetsMD(config->arguments, &argc, argv, "SKYCELLS", "-skycell", "-skycelllist");
+
+    if (!pmConfigFileSetsMD(config->arguments, &argc, argv, "ASTROM", "-astrom", NULL)) { ;
+        if (CHIP_LEVEL_INPUT(stage)) {
+            psError(PS_ERR_UNKNOWN, true, "-astrom is required for raw and chip stages\n");
+            usage();
+        }
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-mask"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        bool nebulousMask = IN_NEBULOUS(argv[argnum]);
+        // This isn't a strict requirement if stage == IPP_STAGE_CHIP
+        if (nebulousMask != nebulousImage) {
+            psError(PS_ERR_UNKNOWN, true, "mask image must be %snebulous path with %s image path\n",
+                nebulousImage ? "" : "non ", nebulousImage ? "nebulous" : "regular");
+            usage();
+        }
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "INPUT.MASK", 0,
+                "name of input mask image", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-chip_mask"))) {
+        if (stage != IPP_STAGE_CHIP) {
+            psError(PS_ERR_UNKNOWN, true, "-chip_mask only allowed at chip stage");
+            usage();
+        }
+        psArgumentRemove(argnum, &argc, argv);
+        bool nebulousMask = IN_NEBULOUS(argv[argnum]);
+        if (nebulousMask != nebulousImage) {
+            psError(PS_ERR_UNKNOWN, true, "chip mask image must be %snebulous path with %s image path\n",
+                nebulousImage ? "" : "non ", nebulousImage ? "nebulous" : "regular");
+            usage();
+        }
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "INPUT.CHMASK", 0,
+                "name of input chip mask image", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-weight"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        bool nebulousWeight = IN_NEBULOUS(argv[argnum]);
+        if (nebulousWeight != nebulousImage) {
+            psError(PS_ERR_UNKNOWN, true, "weight image must have %snebulous path with %s image path\n",
+                nebulousImage ? "" : "non ", nebulousImage ? "nebulous" : "regular");
+            usage();
+        }
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "INPUT.WEIGHT", 0,
+                "name of input weight image", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+    if ((argnum = psArgumentGet(argc, argv, "-sources"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        bool nebulousSources = IN_NEBULOUS(argv[argnum]);
+        if (nebulousSources != nebulousImage) {
+            psError(PS_ERR_UNKNOWN, true, "sources file must have %snebulous path with %s image path\n",
+                nebulousImage ? "" : "non ", nebulousImage ? "nebulous" : "regular");
+            usage();
+        }
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "SOURCES", 0,
+                "name of input sources file", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-stats"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "STATS", 0,
+                "name of input stats file", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-tmproot"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        bool nebulousTmp = IN_NEBULOUS(argv[argnum]);
+        if (gotReplace && (nebulousTmp != nebulousImage)) {
+            psError(PS_ERR_UNKNOWN, true, "tmproot must have %snebulous path with %s image path with -replace\n",
+                nebulousImage ? "" : "non ", nebulousImage ? "nebulous" : "regular");
+            usage();
+        }
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "path for (temporary if replae) output files",
+            argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "-tmproot is required\n");
+        usage();
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-recovery"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "RECOVERY", 0, "path for recovery files",
+            argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    } else if ((stage == IPP_STAGE_RAW) && gotReplace) {
+        psError(PS_ERR_UNKNOWN, true, "-recovery is required for -stage raw with -replace\n");
+        usage();
+    }
+
+    if (argc != 1) {
+        psString unexpectedArguments = NULL;
+        for (int i=1; i<argc; i++) {
+            psStringAppend(&unexpectedArguments, "%s ", argv[i]);
+        }
+        psError(PS_ERR_UNKNOWN, true, "unexpected arguments: %s", unexpectedArguments);
+        psFree(unexpectedArguments);
+        usage();
+    }
+
+    return config;
+}
+
+static void
+updateAstrometry(streakFiles *sf)
+{
+    if (sf->bilevelAstrometry) {
+        if (!linearizeTransforms(sf->astrom)) {
+            // fit failed, leave the transform in the file unchanged
+            return;
+        }
+    }
+    if (!pmAstromWriteWCS(sf->outImage->header, sf->inAstrom->fpa, sf->chip, 0.001)) {
+        psError(PS_ERR_UNKNOWN, false, "failed to update astrometry for extension %d", sf->extnum);
+        streaksExit("", PS_EXIT_UNKNOWN_ERROR);
+    }
+    if (sf->outMask) {
+        pmAstromWriteWCS(sf->outMask->header, sf->inAstrom->fpa, sf->chip, 0.001);
+    }
+    if (sf->outWeight) {
+        pmAstromWriteWCS(sf->outWeight->header, sf->inAstrom->fpa, sf->chip, 0.001);
+    }
+}
+
+static void
+setStreakBits(psImage *maskImage, psU32 maskStreak)
+{
+    for (int y=0 ; y < maskImage->numRows; y++) {
+        for (int x=0 ; x < maskImage->numCols; x++) {
+            maskImage->data.PS_TYPE_IMAGE_MASK_DATA[y][x] |= maskStreak;
+        }
+    }
+}
+
+// set the image for the output files to the contents of the corresponding input file.
+static bool
+readAndCopyToOutput(streakFiles *sf, bool exciseAll)
+{
+    if (sf->inImage->pmfile) {
+        // image data from pmFPAfile (diff or warp file)
+        readImageFrom_pmFile(sf);
+        sf->astrom = streakSetAstrometry(sf->astrom, sf->stage, sf->inAstrom->fpa,
+                sf->chip, true, sf->cell->concepts, sf->inImage->numCols, sf->inImage->numRows);
+    } else {
+        // image data directly from psFits
+        readImage(sf->inImage, sf->extnum, sf->stage, false);
+        if (SFILE_IS_IMAGE(sf->inImage)) {
+            sf->astrom = streakSetAstrometry(sf->astrom, sf->stage, sf->inAstrom->fpa, sf->chip, false,
+                sf->inImage->header, sf->inImage->numCols, sf->inImage->numRows);
+        }
+    }
+    if (!sf->astrom) {
+        psError(PS_ERR_UNKNOWN, false, "failed to set up astrometry for extension %d", sf->extnum);
+        streaksExit("", PS_EXIT_UNKNOWN_ERROR);
+    }
+    sf->outImage->header =  psMemIncrRefCounter(sf->inImage->header);
+    if (sf->recImage) {
+        sf->recImage->header = psMetadataCopy(NULL, sf->inImage->header);
+        addRecoveryKeyword(sf->recImage->header);
+    }
+    addDestreakKeyword(sf->outImage->header, true);
+
+    if (!SFILE_IS_IMAGE(sf->inImage)) {
+        // fprintf(stderr, "skipping extnum: %d not an image\n", sf->extnum);
+        // XXX: This extension is probably a video table.
+        copyTable(sf->outImage, sf->inImage, sf->extnum);
+        return false;
+    }
+
+    if (sf->inImage->image) {
+        setupImageRefs(sf->outImage, sf->recImage, sf->inImage, sf->extnum, exciseAll);
+    }  else {
+        // Image cubes are handeled specially
+    }
+
+    // set up the compression parameters
+    copyFitsOptions(sf->outImage, sf->recImage, sf->inImage, sf->tiles);
+
+    if (sf->inMask) {
+        readImage(sf->inMask, sf->extnum, sf->stage, true);
+        if (sf->outMask) {
+            sf->outMask->header = (psMetadata*) psMemIncrRefCounter(sf->inMask->header);
+            if (sf->recMask) {
+                sf->recMask->header = psMetadataCopy(NULL, sf->outMask->header);
+                addRecoveryKeyword(sf->recMask->header);
+            }
+            addDestreakKeyword(sf->outMask->header, true);
+            // Note: we don't excise the mask pixels even if exciseAll is true.
+            setupImageRefs(sf->outMask, sf->recMask, sf->inMask, sf->extnum, false);
+            if (exciseAll) {
+                strkGetMaskValues(sf);
+
+                // add the STREAK bit to the mask image pixels
+                setStreakBits(sf->inMask->image, sf->maskStreak);
+            }
+            if (sf->outChMask) {
+                sf->outChMask->header = (psMetadata *) psMemIncrRefCounter(sf->outMask->header);
+                sf->outChMask->image = (psImage *) psMemIncrRefCounter(sf->outMask->image);
+                if (sf->recChMask) {
+                    sf->recChMask->header = (psMetadata *) psMemIncrRefCounter(sf->recMask->header);
+                    sf->recChMask->image = (psImage *) psMemIncrRefCounter(sf->recMask->image);
+                }
+            }
+
+            copyFitsOptions(sf->outMask, sf->recMask, sf->inMask, sf->tiles);
+            if (sf->outChMask) {
+                copyFitsOptions(sf->outChMask, sf->recChMask, sf->inMask, sf->tiles);
+            }
+        }
+    }
+
+    if (sf->inWeight) {
+        readImage(sf->inWeight, sf->extnum, sf->stage, false);
+        sf->outWeight->header = (psMetadata*) psMemIncrRefCounter(sf->inWeight->header);
+        if (sf->recWeight) {
+            sf->recWeight->header = psMetadataCopy(NULL, sf->outWeight->header);
+            addRecoveryKeyword(sf->recWeight->header);
+        }
+        addDestreakKeyword(sf->outWeight->header, true);
+        setupImageRefs(sf->outWeight, sf->recWeight, sf->inWeight, sf->extnum, exciseAll);
+
+        copyFitsOptions(sf->outWeight, sf->recWeight, sf->inWeight, sf->tiles);
+    }
+
+    return true;
+}
+
+static void
+writeImages(streakFiles *sf, bool exciseImageCube)
+{
+    psString extname = NULL;
+    if (sf->nHDU > 1) {
+        bool mdok;
+        extname = psMetadataLookupStr(&mdok, sf->inImage->header, "EXTNAME");
+    }
+    if (sf->inImage->numZPlanes == 0)  {
+        // note exciseing complete images is handled in readAndCopyToOutput
+        writeImage(sf->outImage, extname, sf->extnum);
+        writeImage(sf->recImage, extname, sf->extnum);
+    } else {
+        // we have an image cube
+        if (exciseImageCube) {
+            // copy the entire input image to the recovery image
+            writeImageCube(sf->recImage, sf->inImage->imagecube, extname, sf->extnum);
+        } else {
+            // otherwise write it to the output
+            writeImageCube(sf->outImage, sf->inImage->imagecube, extname, sf->extnum);
+        }
+
+        // Now deal with the other output image
+        if (exciseImageCube) {
+            // Set the values in the imagecube images to NAN and write them to the output image
+            for (int i = 0; i < psArrayLength(sf->inImage->imagecube); i++) {
+                psImage *image = psArrayGet (sf->inImage->imagecube, i);
+                // XXX: NAN isn't right. It should be the integer equivalent. Should use exciseValue
+                // but it isn't set with this code path. Fix that.
+                psImageInit(image, 65535);
+            }
+            writeImageCube(sf->outImage, sf->inImage->imagecube, extname, sf->extnum);
+        } else {
+            if (sf->recImage) {
+                // Set the values in the imagecube images to zero
+                for (int i = 0; i < psArrayLength(sf->inImage->imagecube); i++) {
+                    psImage *image = psArrayGet (sf->inImage->imagecube, i);
+                    psImageInit(image, 0);
+                }
+                // copy the entire zeroed image to the recovery image
+                writeImageCube(sf->recImage, sf->inImage->imagecube, extname, sf->extnum);
+            }
+        }
+        // XXX: check this
+        // Assumption: there are no mask and weight images for video cells
+        return;
+    }
+    if (sf->outMask) {
+        writeImage(sf->outMask, extname, sf->extnum);
+        writeImage(sf->recMask, extname, sf->extnum);
+        if (sf->stage == IPP_STAGE_CHIP) {
+            writeImage(sf->outChMask, extname, sf->extnum);
+            writeImage(sf->recChMask, extname, sf->extnum);
+        }
+    }
+    if (sf->outWeight) {
+        writeImage(sf->outWeight, extname, sf->extnum);
+        writeImage(sf->recWeight, extname, sf->extnum);
+    }
+}
+
+static void
+excisePixel(streakFiles *sfiles, unsigned int x, unsigned int y, bool streak, psImageMaskType newMaskValue)
+{
+    double exciseValue = sfiles->inImage->exciseValue;
+
+    // we clip so that the streak calculation code doesn't have to
+    if ((x < 0) || (x >= sfiles->inImage->numCols) || (y < 0) || (y >= sfiles->inImage->numRows)) {
+        return;
+    }
+
+    if (sfiles->inImage->image->type.type == PS_TYPE_U16) {
+        psU16 imageValue  = sfiles->inImage->image->data.U16[y][x];
+        if (sfiles->recImage && !isExciseValue(imageValue, sfiles->inImage->exciseValue) ) {
+            sfiles->recImage->image->data.U16[y][x] = imageValue;
+        }
+
+        if (sfiles->transparentStreaks == 0) {
+            sfiles->outImage->image->data.U16[y][x] = exciseValue;
+        } else {
+            if (streak) {
+                // as a visualization aid don't mask the pixel, just change the intensity
+                sfiles->outImage->image->data.U16[y][x] = imageValue + sfiles->transparentStreaks;
+            } else {
+                sfiles->outImage->image->data.U16[y][x] = exciseValue;
+            }
+        }
+    } else if (sfiles->inImage->image->type.type == PS_TYPE_F32) {
+        float imageValue  = sfiles->inImage->image->data.F32[y][x];
+        if (sfiles->recImage && !isExciseValue(imageValue, sfiles->inImage->exciseValue) ) {
+            sfiles->recImage->image->data.F32[y][x] = imageValue;
+        }
+
+        if (sfiles->transparentStreaks == 0) {
+            sfiles->outImage->image->data.F32[y][x] = exciseValue;
+        } else {
+            if (streak) {
+                // as a visualization aid don't mask the pixel, just change the intensity
+                sfiles->outImage->image->data.F32[y][x] = imageValue + sfiles->transparentStreaks;
+            } else {
+                sfiles->outImage->image->data.F32[y][x] = exciseValue;
+            }
+        }
+    } else {
+        // We could handle F64 but I don't think we ever get them.
+        // Should catch this earlier
+        psError(PS_ERR_IO, true, "unexpected image type %x found", sfiles->inImage->image->type.type );
+        streaksExit("", PS_EXIT_PROG_ERROR);
+    }
+
+    if (sfiles->outWeight) {
+        // Assume that weight images are always a floating point type
+        if (sfiles->inWeight->image->type.type == PS_TYPE_F32) {
+            if (sfiles->recWeight) {
+                sfiles->recWeight->image->data.F32[y][x] = sfiles->inWeight->image->data.F32[y][x];
+            }
+            sfiles->outWeight->image->data.F32[y][x] = NAN;
+        } else if (sfiles->inWeight->image->type.type == PS_TYPE_F64) {
+            if (sfiles->recWeight) {
+                sfiles->recWeight->image->data.F64[y][x] = sfiles->inWeight->image->data.F64[y][x];
+            }
+            sfiles->outWeight->image->data.F64[y][x] = NAN;
+        } else {
+            // Should catch this earlier
+            psError(PS_ERR_IO, true, "unexpected weight image type %x found", sfiles->inWeight->image->type.type );
+            streaksExit("", PS_EXIT_PROG_ERROR);
+        }
+    }
+    if (sfiles->outMask) {
+        if (sfiles->recMask) {
+            sfiles->recMask->image->data.PS_TYPE_IMAGE_MASK_DATA[y][x] =
+                sfiles->inMask->image->data.PS_TYPE_IMAGE_MASK_DATA[y][x];
+        }
+        sfiles->outMask->image->data.PS_TYPE_IMAGE_MASK_DATA[y][x] |= newMaskValue;
+    }
+}
+
+static long
+exciseNonDiffedPixels(streakFiles *sfiles, psImageMaskType newMaskValue)
+{
+    int cell_x0 = sfiles->astrom->cell_x0;
+    int cell_y0 = sfiles->astrom->cell_y0;
+    int xParity = sfiles->astrom->xParity;
+    int yParity = sfiles->astrom->yParity;
+    int numCols = sfiles->inImage->numCols; // for raw images this was calculated from the width of datasec
+    int numRows = sfiles->inImage->numRows; // for raw images this was calculated from the height of datasec
+
+    long excisedPixels = 0;
+
+//    printf("%2d x0: %4d y0: %4d xpar: %d ypar: %d\n", sfiles->extnum, cell_x0, cell_y0, xParity, yParity);
+
+    // XXX: in the case of raw images we are making an implicit assumption here
+    // that the DATASEC begins at (x, y) = (0, 0) in the array for this image
+    // While this is true for GPC1 it isn't necessarily true
+    for (int yCell = 0; yCell < numRows; yCell++) {
+        int yChip;
+        if (yParity == 1) {
+            yChip = cell_y0 + yCell;
+        } else {
+            yChip = cell_y0 - yCell;
+        }
+
+        psU8 *pixels = sfiles->diffedPixels->data.U8[yChip];
+
+        if (xParity == 1) {
+            pixels += cell_x0;
+            for (int xCell = 0; xCell < numCols; xCell++, pixels++) {
+                if (! *pixels ) {
+                    excisePixel(sfiles, xCell, yCell, false, newMaskValue);
+                    excisedPixels++;
+                }
+            }
+        } else {
+            // negative parity
+            // point to the right most pixel in chip coordinates (lowest in cell coords)
+            pixels += cell_x0 - numCols;
+            for (int xCell = numCols - 1; xCell >= 0 ; xCell--, pixels++) {
+                if (!*pixels) {
+                    excisePixel(sfiles, xCell, yCell, false, newMaskValue);
+                    excisedPixels++;
+                }
+            }
+        }
+    }
+    return excisedPixels;
+}
+
+static bool
+diffedPixel(streakFiles *sfiles, int x, int y)
+{
+    PixelPos chipCoord;
+
+    if (!CHIP_LEVEL_INPUT(sfiles->stage)) {
+        // if we're here on a skycell image by definition this pixel was diffed
+        return true;
+    }
+
+    // we clip so that the streak calculation code doesn't have to
+    // clipping here insures that we don't touch the overscan regions
+    if ((x < 0) || (x >= sfiles->inImage->numCols) ||
+        (y < 0) || (y >= sfiles->inImage->numRows)) {
+        return false;
+    }
+
+    cellToChipInt(&chipCoord.x, &chipCoord.y, sfiles->astrom, x, y);
+
+    if (chipCoord.x < 0 || chipCoord.x >= sfiles->diffedPixels->numCols) {
+        return false;
+    }
+    if (chipCoord.y < 0 || chipCoord.y >= sfiles->diffedPixels->numRows) {
+        return false;
+    }
+
+    return psImageGet(sfiles->diffedPixels, chipCoord.x, chipCoord.y) ? true : false;
+}
+
+// read a sources file (.cmf) and remove any rows whose coordinate is convered by a
+// streak mask
+static void
+censorSources(streakFiles *sfiles, psImageMaskType maskStreak)
+{
+    if ((!sfiles->inSources) || (!sfiles->outMask)) {
+        return;
+    }
+    psImage *maskImage = sfiles->outMask->image;
+    if (!maskImage) {
+        psError(PS_ERR_IO, false, "maskImage is null!");
+        streaksExit("", PS_EXIT_PROG_ERROR);
+    }
+
+    sFile *in = sfiles->inSources;
+    sFile *out = sfiles->outSources;
+
+    // Primary header, should be "something.hdr"
+    {
+        psMetadata *header = psFitsReadHeader(NULL, in->fits);
+        if (!header) {
+            psError(PS_ERR_IO, false, "failed to read header from %s", in->resolved_name);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+
+        bool status;
+        psString extname = psMetadataLookupStr(&status, header, "EXTNAME");
+        if (!extname) {
+            psError(PS_ERR_IO, false, "failed to find extname in header of %s", in->resolved_name);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        addDestreakKeyword(header, true);
+
+        if (!psFitsWriteBlank(out->fits, header, extname)) {
+            psError(PS_ERR_IO, false, "failed to write blank in header of %s", in->resolved_name);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        psFree(header);
+    }
+
+    // Extension with PSF fits, should be "CLASS_ID.psf"
+    // This assumes that the sources extension comes first
+    {
+        if (!psFitsMoveExtNum(in->fits, 1, true)) {
+            psErrorStackPrint(stderr, "failed to move to extension 1 in from %s", in->resolved_name);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+
+        psMetadata *header = psFitsReadHeader(NULL, in->fits);
+        if (!header) {
+            psErrorStackPrint(stderr, "failed to read header from %s", in->resolved_name);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        psString extname = psMetadataLookupStr(NULL, header, "EXTNAME");
+        if (!extname) {
+            psError(PS_ERR_IO, false, "failed to find extname in header of %s", in->resolved_name);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+
+#ifdef CHECK_MEM_USE
+        bool printStats = false;
+        size_t allocated = 0;
+        size_t persistent = 0;
+        size_t allocatedBefore = psMemStats(printStats, &allocated, &persistent);
+        printf("Before: %12lld %12lld %12lld\n", (long long) allocated, (long long) persistent, (long long) allocatedBefore);
+#endif
+
+        psFitsTable *table = psFitsReadTableNew(in->fits);
+
+#ifdef CHECK_MEM_USE
+        size_t allocatedAfter = psMemStats(printStats, &allocated, &persistent);
+        printf("After:  %12lld %12lld %12lld\n", (long long) allocated, (long long) persistent, (long long) allocatedAfter);
+        printf("Change: %12lld\n", (long long) allocatedAfter - allocatedBefore);
+#endif
+
+        if (!table) {
+            psErrorStackPrint(stderr, "failed to read table in %s", in->resolved_name);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+
+        int numCensored = 0;
+        bool *rowCensored = NULL;
+        if (table->numRows > 0) {
+            // For speed we get intimate with the table
+            // avoid 2 * numRows lookups of the column number by looking
+            // it up ahead of time and verifying that the type is what we
+            // expect.
+            int xCol = psFitsTableGetColumnNumber(table, "X_PSF");
+            assert(xCol >= 0 && xCol < table->numCols);
+            assert(table->columns[xCol].type == PS_DATA_F32);
+
+            int yCol = psFitsTableGetColumnNumber(table, "Y_PSF");
+            assert(yCol >= 0 && yCol < table->numCols);
+            assert(table->columns[yCol].type == PS_DATA_F32);
+
+            // We mark censored rows in this array.
+            rowCensored = psAlloc(table->numRows * sizeof(bool));
+            memset(rowCensored, 0, table->numRows * sizeof(bool));
+
+            for (int i = 0 ; i < table->numRows; i++) {
+
+#ifndef GO_SLOWER
+                psF32 x = table->columns[xCol].data.F32[i];
+                psF32 y = table->columns[yCol].data.F32[i];
+#else
+                psF32 x = psFitsTableGetF32(NULL, table, i, "X_PSF");
+                psF32 y = psFitsTableGetF32(NULL, table, i, "Y_PSF");
+#endif
+                psImageMaskType mask;
+                if ((x >= maskImage->numCols) || (y >= maskImage->numRows) || (x <  0) || (y < 0) || isnan(x) || isnan(y)) {
+                    mask = maskStreak;
+                } else {
+                    mask = maskImage->data.PS_TYPE_IMAGE_MASK_DATA[(int)y][(int)x];
+                }
+
+                // Key the source if the center pixel is not masked with maskStreak
+                if (mask & maskStreak) {
+                    rowCensored[i] = true;
+                    numCensored++;
+                }
+            }
+
+            // remove the censored rows from the table
+            if (!psFitsTableCensor(table, rowCensored)) {
+                streaksExit("", PS_EXIT_PROG_ERROR);
+            }
+
+            addDestreakKeyword(header, true);
+        }
+
+        if (table->numCols > 0) {
+            printf("Censored %d sources\n", numCensored);
+            if (! psFitsWriteTableNew(out->fits, header, table, extname)) {
+                psErrorStackPrint(stderr, "failed to write table to %s", out->resolved_name);
+                streaksExit("", PS_EXIT_DATA_ERROR);
+            }
+        } else {
+            printf("No input sources\n");
+            // We'd like to write a blank table, but this is as good as it gets without more work to parse the
+            // header and create dummy columns.
+            if (!psFitsWriteBlank(out->fits, header, extname)) {
+                psErrorStackPrint(stderr, "failed to write empty header to %s", out->resolved_name);
+                streaksExit("", PS_EXIT_DATA_ERROR);
+            }
+        }
+        psFree(header);
+        psFree(table);
+        psFree(rowCensored);
+    }
+
+    // Copy any other extensions in the file (deteff for example) to the output
+    // XXX: This assumes that all we have is binary tables.
+    for (int i = 2; i< in->nHDU; i++) {
+        if (!psFitsMoveExtNum(in->fits, 1, true)) {
+            psErrorStackPrint(stderr, "failed to move to extension number %d in %s", i, in->resolved_name);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        psMetadata *header = psFitsReadHeader(NULL, in->fits);
+        if (!header) {
+            psErrorStackPrint(stderr, "failed to read header from %s", in->resolved_name);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        psString extname = psMetadataLookupStr(NULL, header, "EXTNAME");
+        if (!extname) {
+            psError(PS_ERR_IO, false, "failed to find extname in header of %s", in->resolved_name);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+
+        psFitsTable *table = psFitsReadTableNew(in->fits);
+        if (!table) {
+            psErrorStackPrint(stderr, "failed to read table in %s", in->resolved_name);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        if (! psFitsWriteTableNew(out->fits, header, table, extname)) {
+            psErrorStackPrint(stderr, "failed to write table to %s", out->resolved_name);
+            streaksExit("", PS_EXIT_DATA_ERROR);
+        }
+        psFree(header);
+        psFree(table);
+    }
+
+    if (!psFitsClose(out->fits)) {
+        psErrorStackPrint(stderr, "failed to close table %s", out->resolved_name);
+        streaksExit("", PS_EXIT_DATA_ERROR);
+    }
+    out->fits = NULL;   // freed by psFitsclose()
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksremove.h
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksremove.h	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksremove.h	(revision 41117)
@@ -0,0 +1,103 @@
+#ifndef STREAKS_H
+#define STREAKS_H
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "string.h"
+#include "pslib.h"
+#include "psmodules.h"
+#include "nebclient.h"
+
+#include "streaksextern.h"
+#include "streaksastrom.h"
+
+typedef struct {
+    psString    resolved_name;
+    psString    name;
+    bool        inNebulous;
+    bool        write;
+    psFits      *fits;
+    int         nHDU;
+    psString    extname;
+    psMetadata  *header;
+    int         numZPlanes;
+    psImage     *image;
+    psArray     *imagecube;
+    pmFPAfile   *pmfile;
+    int         col0;
+    int         numCols;    // number of columns in data section of image (doesn't include bias section)
+    int         xParity;
+    int         row0;
+    int         numRows;    // number of rows in data section of image (does not include bias section)
+    int         yParity;
+    double      exciseValue;
+    psString    program;
+} sFile;
+
+// used for error messages
+extern char * streaksProgram;
+
+typedef struct {
+    pmConfig *config;
+    ippStage stage;
+    int     extnum;
+    int     nHDU;   // number of HDUs in the inputs (all must be equal)
+    sFile *inImage;
+    sFile *inMask;
+    sFile *inWeight;
+    sFile *outImage;
+    sFile *outMask;
+    sFile *outWeight;
+    sFile *recImage;
+    sFile *recMask;
+    sFile *recWeight;
+    sFile *inChMask;    // for chip stage input mask is camera mask and output goes to both chip and cam masks
+    sFile *outChMask;
+    sFile *recChMask;
+    sFile *inSources;
+    sFile *outSources;
+    psMetadata *stats;
+    FILE *statsFile;
+    psString class_id;
+    pmFPAfile  *inAstrom;
+    strkAstrom *astrom;
+    bool  bilevelAstrometry;
+    pmFPAview *view;
+    pmChip  *chip;  // current chip
+    pmCell  *cell;  // current cell
+    psImage *diffedPixels;
+    psVector    *tiles;
+    double  transparentStreaks;
+    psString    program_name;
+    psU32   maskStreak;
+    psU32   maskMask;
+} streakFiles;
+
+#include "streaksio.h"
+
+extern void setupAstrometry(streakFiles *);
+extern strkAstrom *streakSetAstrometry(strkAstrom *, ippStage stage, pmFPA *, pmChip *, bool fromCell, psMetadata *md,
+    int numCols, int numRows);
+// can't declare this in streaksastrom due to header file ordering
+extern void cellToChip(double *xChip, double *yChip, strkAstrom *astrom, double xCell, double yCell);
+extern void cellToChipInt(unsigned int *xChip, unsigned int *yChip, strkAstrom *astrom, int xCell, int yCell);
+
+extern bool computeDiffedPixels(streakFiles *sf);
+extern void streaksExit(psString, int);
+extern ippStage parseStage(psString);
+extern psString pathToDirectory(char *path);
+extern void setStreakFiles( streakFiles *);
+
+extern bool streaksVersionHeaderFull(psMetadata *header);
+extern psString streaksVersionLong(void);
+
+#define CHIP_LEVEL_INPUT(_stage) ((_stage == IPP_STAGE_RAW) || (_stage == IPP_STAGE_CHIP))
+#define USE_SUPPLIED_ASTROM(_stage) CHIP_LEVEL_INPUT(_stage)
+
+#define SFILE_IS_IMAGE(_sfile) (_sfile->image || _sfile->imagecube)
+#define IN_NEBULOUS(_filename) (!strncasecmp(_filename, "neb://", strlen("neb://")))
+
+
+#endif // STREAKS_H
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksreplace.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksreplace.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksreplace.c	(revision 41117)
@@ -0,0 +1,388 @@
+#include "streaksremove.h"
+
+static pmConfig *parseArguments(int, char **);
+static bool readAndCopyToOutput(streakFiles *sf, bool restoreImageCube);
+static void writeImages(streakFiles *sf, bool exciseImageCube);
+static bool readAndCopyToOutput(streakFiles *sf, bool restoreImageCube);
+
+char *streaksProgram = "streaksreplace";
+
+int main(int argc, char *argv[])
+{
+    bool status;
+
+    psLibInit(NULL);
+
+    pmConfig *config = parseArguments(argc, argv);
+    if (!config) {
+        psError(PS_ERR_UNKNOWN, false, "failed to parse arguments\n");
+        return PS_EXIT_CONFIG_ERROR;
+    }
+
+    psMetadata *masks = psMetadataLookupMetadata(&status, config->recipes, "MASKS");
+    if (!status) {
+        psError(PM_ERR_CONFIG, false, "failed to lookup MASKS in recipes\n");
+        return PS_EXIT_CONFIG_ERROR;
+    }
+    // TODO: perhaps we should save this in the image header and get it from there?
+    double maskStreak = (double) psMetadataLookupU8(&status, masks, "STREAK");
+    if (!status) {
+        psError(PM_ERR_CONFIG, false, "failed to lookup mask value for STREAK in recipes\n");
+        return PS_EXIT_CONFIG_ERROR;
+    }
+
+    streakFiles *sfiles = openFiles(config, false, argv[0]);
+
+    if (sfiles->stage == IPP_STAGE_RAW) {
+        // copy PHU to output files
+        copyPHU(sfiles, false, false);
+
+        // advance to the first image extension
+        if (!streakFilesNextExtension(sfiles)) {
+            psErrorStackPrint(stderr, "failed to advance to first extension of input images");
+            exit (PS_EXIT_PROG_ERROR);
+        }
+    }
+
+    // Iterate through components of input files
+    do {
+        bool restoreImageCube = false;
+
+        // read the images and copy the data from the inputs to the outputs
+        // This also sets up the astrometry and initializes the recovery image(s)
+
+        if (!readAndCopyToOutput(sfiles, restoreImageCube)) {
+            // false return value indicates that this extension is not an image and
+            // the contents has already been copied to the output file.
+            // we don't need to process this extesion for streaks.
+            continue;
+        }
+
+        // If pixel in recovery image is not NAN, then it contains an excised pixel
+        // that has been stored in the recovery image
+        for (int y=0; y < sfiles->inImage->numRows; y++) {
+            for (int x=0; x < sfiles->inImage->numCols; x++) {
+                double recValue = psImageGet(sfiles->recImage->image, x, y);
+                if (!isnan(recValue)) {
+                    psImageSet(sfiles->inImage->image, x, y, recValue);
+                }
+                if (sfiles->inMask) {
+                    double recMaskValue = psImageGet(sfiles->recMask->image, x, y);
+                    if (recValue != maskStreak) {
+                        psImageSet(sfiles->inMask->image, x, y, recMaskValue);
+                    }
+                }
+                if (sfiles->inWeight) {
+                    double recWeightValue = psImageGet(sfiles->recWeight->image, x, y);
+                    if (!isnan(recWeightValue)) {
+                        psImageSet(sfiles->inWeight->image, x, y, recWeightValue);
+                    }
+                }
+            }
+        }
+
+        // write out the re-streaked images
+        writeImages(sfiles, restoreImageCube);
+
+    } while (streakFilesNextExtension(sfiles));
+
+    // check the weight and mask files for extra extensions that might be in files
+    // (covariance matrix for example)
+    copyExtraExtensions(sfiles);
+
+    // close all files
+    closeImages(sfiles);
+
+    if (!replicateOutputs(sfiles)) {
+        psErrorStackPrint(stderr, "failed to replicate output files");
+        exit(PS_EXIT_UNKNOWN_ERROR);
+    }
+
+//    nebServerFree(ourNebServer);
+    psFree(config);
+    pmConceptsDone();
+    pmConfigDone();
+    psLibFinalize();
+
+    //  PAU
+    fprintf(stderr, "Found %d leaks at %s\n", psMemCheckLeaks (0, NULL, NULL, false), "streaksreplace");
+
+    return 0;
+}
+static void usage(void)
+{
+    fprintf(stderr, "USAGE: streaksreplace -stage raw|chip|warp|diff -image IMAGE.fits -tmproot directory\n\n");
+    fprintf(stderr, "Optional arguments:\n");
+    fprintf(stderr, "\t-mask MASK.fits mask file to re-streak\n");
+    fprintf(stderr, "\t-recmask RECOVERYMASK.fits: recovery mask file (required with -mask)\n");
+    fprintf(stderr, "\t-weight WEIGHT.fits: weight file to re-streak\n");
+    fprintf(stderr, "\t-recweight RECOVERYWEIGHT.fits: recovery weight file (required with -weight)\n");
+    fprintf(stderr, "\t-replace: replace the input images with the output\n");
+    fprintf(stderr, "\t-remove: remove the original image after processing (requires -replace)\n");
+
+    exit(2);
+}
+
+static pmConfig *parseArguments(int argc, char **argv)
+{
+    pmConfig *config = pmConfigRead(&argc, argv, NULL);
+    if (!config) {
+        streaksExit("failed to parse arguments", PS_EXIT_CONFIG_ERROR);
+    }
+
+    int argnum;
+    ippStage stage = IPP_STAGE_NONE;
+    if ((argnum = psArgumentGet(argc, argv, "-stage"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        stage = parseStage(argv[argnum]);
+        psMetadataAddS32(config->arguments, PS_LIST_TAIL, "STAGE", 0,
+                "pipeline stage for streak removal", stage);
+        psArgumentRemove(argnum, &argc, argv);
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "-stage is required\n");
+        usage();
+    }
+
+    // If we are asked to replace inputs with the outputs and input image is in nebulous
+    // we require mask, weight, and tmproot to all be
+    bool nebulousImage = false;
+    if ((argnum = psArgumentGet(argc, argv, "-image"))) {
+        // for raw and chip level images we use psFits for I/O and ...
+        nebulousImage = IN_NEBULOUS(argv[argnum+1]);
+        if (CHIP_LEVEL_INPUT(stage)) {
+            psArgumentRemove(argnum, &argc, argv);
+            psMetadataAddStr(config->arguments, PS_LIST_TAIL, "INPUT", 0,
+                    "name of input image", argv[argnum]);
+            psArgumentRemove(argnum, &argc, argv);
+        } else  {
+            // ... for warped images we use pmFPAfiles
+            if (!pmConfigFileSetsMD(config->arguments, &argc, argv, "INPUT", "-image", NULL)) { ;
+                psError(PS_ERR_UNKNOWN, false, "failed to process -image");
+                usage();
+            }
+        }
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "-image is required\n");
+        usage();
+    }
+
+    bool gotReplace = false;
+    if ((argnum = psArgumentGet(argc, argv, "-replace"))) {
+        gotReplace = true;
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddBool(config->arguments, PS_LIST_TAIL, "REPLACE", 0, "replace input files",
+            true);
+    }
+
+    bool gotMask = false;
+    if ((argnum = psArgumentGet(argc, argv, "-mask"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        bool nebulousMask = IN_NEBULOUS(argv[argnum]);
+        if (nebulousMask != nebulousImage) {
+            psError(PS_ERR_UNKNOWN, true, "mask image must be %snebulous path with %s image path\n",
+                nebulousImage ? "" : "non ", nebulousImage ? "nebulous" : "regular");
+            usage();
+        }
+        gotMask = true;
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "INPUT.MASK", 0,
+                "name of input mask image", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    bool gotWeight = false;
+    if ((argnum = psArgumentGet(argc, argv, "-weight"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        bool nebulousWeight = IN_NEBULOUS(argv[argnum]);
+        if (nebulousWeight != nebulousImage) {
+            psError(PS_ERR_UNKNOWN, true, "weight image must have %snebulous path with %s image path\n",
+                nebulousImage ? "" : "non ", nebulousImage ? "nebulous" : "regular");
+            usage();
+        }
+        gotWeight = true;
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "INPUT.WEIGHT", 0,
+                "name of input weight image", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-tmproot"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        bool nebulousTmp = IN_NEBULOUS(argv[argnum]);
+        if (gotReplace && (nebulousTmp != nebulousImage)) {
+            psError(PS_ERR_UNKNOWN, true, "tmproot must have %snebulous path with %s image path with -replace\n",
+                nebulousImage ? "" : "non ", nebulousImage ? "nebulous" : "regular");
+            usage();
+        }
+        psString dir = pathToDirectory(argv[argnum]);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "OUTPUT", 0, "directory for temporary files",
+            dir);
+        psArgumentRemove(argnum, &argc, argv);
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "-tmproot is required\n");
+        usage();
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-recimage"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "RECOVERY.IMAGE", 0, "recovery image file name",
+            argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "-recimage is required\n");
+        usage();
+    }
+    if ((argnum = psArgumentGet(argc, argv, "-recmask"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        bool nebulousMask = IN_NEBULOUS(argv[argnum]);
+        if (nebulousMask != nebulousImage) {
+            psError(PS_ERR_UNKNOWN, true, "mask image must be %snebulous path with %s image path\n",
+                nebulousImage ? "" : "non ", nebulousImage ? "nebulous" : "regular");
+            usage();
+        }
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "RECOVERY.MASK", 0,
+                "name of input mask image", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    } else if (gotMask) {
+        psError(PS_ERR_UNKNOWN, true, "-recmask is required with -mask\n");
+        usage();
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-recweight"))) {
+        psArgumentRemove(argnum, &argc, argv);
+        bool nebulousWeight = IN_NEBULOUS(argv[argnum]);
+        if (nebulousWeight != nebulousImage) {
+            psError(PS_ERR_UNKNOWN, true, "weight image must have %snebulous path with %s image path\n",
+                nebulousImage ? "" : "non ", nebulousImage ? "nebulous" : "regular");
+            usage();
+        }
+        psMetadataAddStr(config->arguments, PS_LIST_TAIL, "RECOVERY.WEIGHT", 0,
+                "name of input weight image", argv[argnum]);
+        psArgumentRemove(argnum, &argc, argv);
+    } else if (gotWeight) {
+        psError(PS_ERR_UNKNOWN, true, "-recweight is required with -weight\n");
+        usage();
+    }
+
+    if ((argnum = psArgumentGet(argc, argv, "-remove"))) {
+        if (!gotReplace) {
+            psError(PS_ERR_UNKNOWN, true, "-replace is required with -remove\n");
+            usage();
+        }
+        psArgumentRemove(argnum, &argc, argv);
+        psMetadataAddBool(config->arguments, PS_LIST_TAIL, "REMOVE", 0, "remove original files",
+            true);
+    }
+
+    if (argc != 1) {
+        psString unexpectedArguments = NULL;
+        for (int i=1; i<argc; i++) {
+            psStringAppend(&unexpectedArguments, "%s ", argv[i]);
+        }
+        psError(PS_ERR_UNKNOWN, true, "unexpected arguments: %s", unexpectedArguments);
+        psFree(unexpectedArguments);
+        usage();
+    }
+
+    return config;
+}
+
+// set the image for the output files to the contents of the corresponding input file.
+static bool readAndCopyToOutput(streakFiles *sf, bool restoreImageCube)
+{
+    if (sf->inImage->pmfile) {
+        // image data from pmFPAfile (diff or warp file)
+        readImageFrom_pmFile(sf);
+    } else {
+        // image data directly from psFits
+        readImage(sf->inImage, sf->extnum, sf->stage, false);
+    }
+    // read the recovery pixels
+    readImage(sf->recImage, sf->extnum, sf->stage, false);
+
+    sf->outImage->header = (psMetadata*) psMemIncrRefCounter(sf->inImage->header);
+    addDestreakKeyword(sf->outImage->header, false);
+
+    if (!SFILE_IS_IMAGE(sf->inImage)) {
+        // fprintf(stderr, "skipping extnum: %d not an image\n", sf->extnum);
+        // XXX: This extension is probably a video table.
+        copyTable(sf->outImage, sf->inImage, sf->extnum);
+        return false;
+    }
+
+    if (sf->inImage->image) {
+        setupImageRefs(sf->outImage, NULL, sf->inImage, sf->extnum, false);
+    }  else {
+        // Image cubes are handeled specially
+    }
+
+    // set up the compression parameters
+    copyFitsOptions(sf->outImage, sf->recImage, sf->inImage, sf->tiles);
+
+    if (sf->inMask) {
+        readImage(sf->inMask, sf->extnum, sf->stage, true);
+        readImage(sf->recMask, sf->extnum, sf->stage, true);
+        sf->outMask->header = (psMetadata*) psMemIncrRefCounter(sf->inMask->header);
+        
+        addDestreakKeyword(sf->outMask->header, false);
+
+        setupImageRefs(sf->outMask, NULL, sf->inMask, sf->extnum, false);
+
+        copyFitsOptions(sf->outMask, NULL, sf->inMask, sf->tiles);
+    }
+
+    if (sf->inWeight) {
+        readImage(sf->inWeight, sf->extnum, sf->stage, false);
+        readImage(sf->recWeight, sf->extnum, sf->stage, false);
+
+        sf->outWeight->header = (psMetadata*) psMemIncrRefCounter(sf->inWeight->header);
+        addDestreakKeyword(sf->outWeight->header, false);
+        setupImageRefs(sf->outWeight, NULL, sf->inWeight, sf->extnum, false);
+
+        copyFitsOptions(sf->outWeight, NULL, sf->inWeight, sf->tiles);
+    }
+
+    return true;
+}
+
+static void writeImages(streakFiles *sf, bool exciseImageCube)
+{
+    psString extname = NULL;
+    if (sf->nHDU > 1) {
+        bool mdok;
+        extname = psMetadataLookupStr(&mdok, sf->inImage->header, "EXTNAME");
+    }
+    if (sf->inImage->numZPlanes == 0)  {
+        // note exciseing complete images is handled in readAndCopyToOutput
+        writeImage(sf->outImage, extname, sf->extnum);
+    } else {
+#ifdef TODO
+        // we have an image cube
+        double initValue;
+        // otherwise write it to the output
+        writeImageCube(sf->outImage, sf->recImage->imagecube, extname, sf->extnum);
+
+        // borrow one of the images from the imagecube and set it to init value
+        psImage *image = psArrayGet (sf->inImage->imagecube, 0);
+        psMemIncrRefCounter(image);
+        psImageInit(image, initValue);
+        if (exciseImageCube) {
+            sf->outImage->image = image;
+            writeImage(sf->outImage, extname, sf->extnum);
+        } else {
+            // write zero valued image to reccovery
+            if (sf->recImage) {
+                sf->recImage->image = image;
+                writeImage(sf->recImage, extname, sf->extnum);
+            }
+        }
+#endif
+        // Assumption: there are no mask and weight images for video cells because they only appear
+        // at the raw stage. TODO: insure this
+        return;
+    }
+    if (sf->outMask) {
+        writeImage(sf->outMask, extname, sf->extnum);
+    }
+    if (sf->outWeight) {
+        writeImage(sf->outWeight, extname, sf->extnum);
+    }
+}
Index: /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksutil.c
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksutil.c	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/remove/src/streaksutil.c	(revision 41117)
@@ -0,0 +1,52 @@
+#include "streaksremove.h"
+
+psString pathToDirectory(char *path)
+{
+    // insure that path ends in /
+    int len = strlen(path);
+    psString dir = NULL;
+    if (strrchr(path, '/') != (path + len - 1)) {
+        psStringAppend(&dir, "%s/", path);
+    } else {
+        dir = psStringCopy(path);
+    }
+    return dir;
+}
+
+ippStage
+parseStage(psString stageStr)
+{
+    if (!strcmp("raw", stageStr)) {
+        return IPP_STAGE_RAW;
+    } else if (!strcmp("chip", stageStr)) {
+        return IPP_STAGE_CHIP;
+    } else if (!strcmp("warp", stageStr)) {
+        return IPP_STAGE_WARP;
+    } else if (!strcmp("diff", stageStr)) {
+        return IPP_STAGE_DIFF;
+    } else {
+        psError(PS_ERR_UNKNOWN, true, "unknown stage string: %s\n", stageStr);
+        streaksExit("", PS_EXIT_DATA_ERROR);
+        // notreached
+        return IPP_STAGE_NONE;
+    }
+}
+
+streakFiles *ourStreakFiles = NULL;
+
+void
+setStreakFiles(streakFiles *sfiles)
+{
+    ourStreakFiles = sfiles;
+}
+
+// to enhance clarity in these programs we don't propagate errors up the stack
+// we just bail out
+void streaksExit(psString str, int exitCode) {
+    psErrorStackPrint(stderr, "%s", str);
+    if (ourStreakFiles) {
+        deleteTemps(ourStreakFiles);
+    }
+    exit(exitCode);
+}
+
Index: /branches/ccl_branches/ipponly-20191108/magic/verify/Makefile
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/verify/Makefile	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/verify/Makefile	(revision 41117)
@@ -0,0 +1,38 @@
+TARGETS =  VerifyStreaks
+
+CXX = c++
+CXXFLAGS = -W -Wall -O3 -DNDEBUG
+# CXXFLAGS = -W -Wall -g
+
+QTLIB_INC = /home/panstarrs/ipp/src/qtlib/include
+QTLIB_LIB = /home/panstarrs/ipp/src/qtlib/lib
+
+SSA_SRC = ../ssa-core-cpp/src
+SSA_LIB = ../ssa-core-cpp/build
+MAGIC_SRC = ../magic/
+
+PSLIB_DIR = $(PSCONFDIR)/$(PSCONFIG)/lib
+PSINC_DIR = $(PSCONFDIR)/$(PSCONFIG)/include
+
+CINC = -I. -I$(QTLIB_INC) -I$(SSA_SRC) -I$(PSINC_DIR) -I$(MAGIC_SRC)
+CLIB = -L. -L$(QTLIB_LIB) -L$(SSA_LIB) -I$(PSINC_DIR) -lcfitsio -lSSA -lQtCore -lQtGui
+
+all : $(TARGETS)
+
+% :: %.cpp
+	$(CXX) $(CXXFLAGS) $(CINC) $< $(CLIB) -o $@
+
+.PHONY : clean
+
+.SILENT : clean
+
+clean :
+	   - rm $(TARGETS)
+
+DESTDIR=$(PSCONFDIR)/$(PSCONFIG)
+
+install: $(TARGETS)
+	cp VerifyStreaks $(DESTDIR)/bin
+	chmod 755  $(DESTDIR)/bin/VerifyStreaks
+	cp verifystreaks.sh $(DESTDIR)/bin
+	chmod 755  $(DESTDIR)/bin/verifystreaks.sh
Index: /branches/ccl_branches/ipponly-20191108/magic/verify/verifystreaks.sh
===================================================================
--- /branches/ccl_branches/ipponly-20191108/magic/verify/verifystreaks.sh	(revision 41117)
+++ /branches/ccl_branches/ipponly-20191108/magic/verify/verifystreaks.sh	(revision 41117)
@@ -0,0 +1,21 @@
+#!/bin/csh -f
+
+if ($#argv != 4) then 
+  echo "USAGE: verifystreaks.sh (workdir) (exp_id) (magic_id) (outdir)"
+  exit 1
+endif
+
+set workdir = $1
+set expID = $2
+set magicID = $3
+set outdir = $4
+
+mkdir -p $outdir
+
+# make clusters file:
+ls $workdir/$expID/$expID.mgc.$magicID.*.clusters | sed s/.clusters// > $outdir/clusters.list
+
+# run VerifyStreaks
+
+VerifyStreaks --out $outdir --clusters $outdir/clusters.list $workdir/$expID/$expID.mgc.$magicID.root.streakMap
+
